Scripts:Fire projectile

From OHRRPGCE-Wiki
Jump to navigation Jump to search
Projectile distance.gif

The following script launches a slice in an arc according to gravity.

There's also a simple "jump hero to" (and "jump npc to") script which makes this easy to use for heroes and NPCs.

To use the scripts, you create a slice sl (such as an arrow sprite), place it at the initial position (e.g. the player's position), call fire projectile(sl, speed, x, y, z) (z is optional) which is similar to move slice by(sl, x, y, distance/speed), and then call projectile eachtick(sl) every tick after that. For example,

 fire projectile(sl, 5, 20, 40)   # Move sl right 20 pixels, down 40 pixels, at 5 horizonal pixels per tick
 while (projectile eachtick(sl)) do (wait(1))

There are more complete examples below.

  • Here is an example .rpg file and scripts. (See recording to the right.)

The basic scripts for this example .rpg are included at the bottom of this page, showing how to have multiple projectiles at once.

The scripts[edit]

# Strength of gravity, in hundredths of a pixel per tick per tick
define constant(150, gravity 100)

# Unique slice lookup code used by projectiles 
define constant(12345, sli:dataslice)

################################################################################
# Utility scripts

# These are temporary variables used by the projectile scripts. They are saved
# and loaded to a hidden data slice, so that you can have multiple projectiles at once.
global variable(16001, prj:time)   # Ticks remaining
global variable(16002, prj:x100)   # Current x position * 100
global variable(16003, prj:y100)   # Current y position * 100
global variable(16004, prj:z100)   # Current z position (relative to start) * 100
global variable(16005, prj:v_x100) # x velocity * 100 
global variable(16006, prj:v_y100) # y velocity * 100 
global variable(16007, prj:v_z100) # z velocity * 100 

# Load the prj:* globals from a slice
script, load projectile data, dataslice, begin
    prj:time   := slicewidth(dataslice)
    prj:x100   := sliceheight(dataslice)
    prj:y100   := slicex(dataslice)
    prj:z100   := slicey(dataslice)
    prj:v_x100 := gettoppadding(dataslice)
    prj:v_y100 := getbottompadding(dataslice)
    prj:v_z100 := getleftpadding(dataslice)
end

# Save the prj:* globals to a slice
script, save projectile data, dataslice, begin
    setslicewidth    (dataslice, prj:time)
    setsliceheight   (dataslice, prj:x100)
    setslicex        (dataslice, prj:y100)
    setslicey        (dataslice, prj:z100)
    settoppadding    (dataslice, prj:v_x100)
    setbottompadding (dataslice, prj:v_y100)
    setleftpadding   (dataslice, prj:v_z100)
end

# Create a hidden child slice that can be used to store data
script, create data slice, parent, begin
    variable(dataslice)
    dataslice := lookup slice(sli:dataslice, parent)
    if (dataslice) then (exit returning(dataslice))  # Just in case you call this twice
    dataslice := createcontainer
    setparent(dataslice, parent)
    setslicevisible(dataslice, false)
    setslicelookup(dataslice, sli:dataslice)
    return(dataslice)
end

################################################################################
# Projectiles

# Start a new projectile on an arcing path.
# Speed is the ground speed in pixels per tick.
# x distance and y distance is how far you want to shoot the projectile, relative
# to the initial position (like "move slice by").
# z distance is used when shooting at a target with a different height, e.g. aiming
# at the top of a tree or up a cliff. Positive z distance means shooting upwards
script, fire projectile, slice, speed, x distance, y distance, z distance = 0, begin
    variable(dist, time)

    dist := sqrt(xdistance ^ 2 + ydistance ^ 2)

    # Calculate time-in-air, in ticks
    time := (dist + speed / 2) / speed
    if (time <= 0) then (time := 1)

    # Initial vertical velocity, in hundredths of a pixel per tick
    prj:v_z100 := gravity100 * time / 2 + 100 * zdistance / time

    # Save some other necessary data
    prj:x100 := slicex(slice) * 100
    prj:y100 := slicey(slice) * 100
    prj:z100 := 0
    prj:v_x100 := xdistance * 100 / time
    prj:v_y100 := ydistance * 100 / time
    prj:time := time

    save projectile data(create data slice(slice))
end

# Update the position of a projectile slice. Needs to be called every tick.
# Returns true if still in flight, or false if the projectile has reached the end.
script, projectile eachtick, slice, begin
    variable(dataslice)
    dataslice := lookup slice(sli:dataslice, slice)
    if (dataslice == false) then (script error($99="Couldn't find projectile data slice"))

    load projectile data(dataslice)
    if (prj:time <= 0) then (exit returning(false))  # Already finished
    prj:time -= 1
    prj:x100 += prj:v_x100
    prj:y100 += prj:v_y100
    prj:z100 += prj:v_z100 -- gravity100 / 2  # Add half of gravity to perform leapfrog integration
    prj:v_z100 -= gravity100
    save projectile data(dataslice)

    # Add 50 to round to nearest pixel
    put slice(slice, (prj:x100 + 50) / 100, (prj:y100 -- prj:z100 + 50) / 100)

    return (prj:time > 0)
end

jump hero to script[edit]

This is a very simple way to use the script to make a hero jump in an arc.

This script works for NPCs too, if you just search-and-replace "hero" with "npc".

You might want to change the hero speed before calling the script, and don't forget that you can customise the value of the gravity100 constant.

Note: You need to call suspend player before calling this script, or set npc moves(npc, false) or suspend npcs before using it on an NPC.

# Make a hero to jump to a certain tile position, at their normal walk speed,
# and wait for the jump to finish.
# This is a drop-in replacement for "walk hero to X(who, X), walk hero to Y(who, Y), wait for hero(who)"
# X, Y is the position in tile coordinates.
script, jump hero to, who, x, y, begin
	variable(sl, midair)
	sl := get hero slice(who)
	fire projectile(sl, getherospeed(who), 20 * (x -- herox(who)), 20 * (y -- heroy(who)))
	midair := true
	while (midair) do (
		midair := projectile eachtick(sl)
		# This is needed because you can't move a hero slice directly
		put hero(who, slicex(sl), slicey(sl))
		wait(1)
	)
end

Example Usage[edit]

Here are two more examples of how to use fireprojectile directly: the first uses an onkeypress script to create the projectile, and a timer to run projectile eachtick. Set arrow onkeypress as the map on-keypress script to use it, and press Z to fire.

The second uses a while loop to check for keypresses and update projectiles. Set main loop as the map autorun script to use it, and press Z to fire.


# Set this to the correct map layer, above the hero
define constant(sl:map layer1, projectile layer)

define constant(80, arrow distance)


script, dirX, dir, dist, begin
    switch (dir) do (
        case (up, down) do (return (0))
        case (right) do (return (dist))
        case (left) do (return (-1 * dist))
    )
end

script, dirY, dir, dist, begin
    switch (dir) do (
        case (left, right) do (return (0))
        case (down) do (return (dist))
        case (up) do (return (-1 * dist))
    )
end


################################################################################
# Here is one way to run the scripts above: by using a timer.
# Each projectile in the air will need a different timer and a different global variable.

defineconstant(15, timer:arrow1)
globalvariable(1, arrow1)

plotscript, arrow onkeypress, begin
    # Press Z to shoot an arrow. Can only fire one arrow at a time.
    if (keyval(key:z) > 1 && arrow1 == 0) then (fire arrow1)
end

script, fire arrow1, begin
    arrow1 := load walkabout sprite(3)
    setparent(arrow1, lookupslice(projectile layer))
    put slice(arrow1, heropixelx, heropixely)

    variable(x, y)
    x := dirX(herodirection, arrow distance)  # Find the position 60 pixels ahead of the player
    y := dirY(herodirection, arrow distance)
    fire projectile(arrow1, 8, x, y)  # 8 pixel/tick 
    set timer(timer:arrow1, 0, 1, @arrow1 eachtick)
end

script, arrow1 eachtick, timer id, begin
    if (projectile eachtick(arrow1)) then (
        # Still in the air, run timer again
        set timer(timer id, 0)  # Restarts the timer with a count of 0 (next tick)
    ) else (
        # Hit the ground,c hange to a 'splash' animation 
        set timer(timer:arrow1, 0, 1, @arrow1 splash eachtick)
    )
end 

# Play a three-frame splash animation
script, arrow1 splash eachtick, timer id, begin
    setspriteframe(arrow1, getspriteframe(arrow1) + 1)
    if (getspriteframe(arrow1) == 4) then (
        free slice(arrow1)
        arrow1 := 0
    ) else (
        set timer(timer id, 0)
    )
end

################################################################################
# Here's another way: by using a loop

# slice lookup code used by projectiles (normally you would define this in Custom instead)
define constant(1000, sli:projectile)
define constant(1001, sli:splash)  # Projectile splash animation

script, fire arrow, begin
    variable(arrow)
    arrow := load walkabout sprite(3)
    setparent(arrow, lookupslice(projectile layer))
    put slice(arrow, heropixelx, heropixely)
    setslicelookup(arrow, sli:projectile)  # Set this so we can find it later

    variable(x, y)
    x := dirX(herodirection, arrow distance)  # Find the position 60 pixels ahead of the player
    y := dirY(herodirection, arrow distance)
    fire projectile(arrow, 8, x, y)  # 8 pixel/tick 
end

plotscript, main loop, begin
    while (true) do (
        if (keyval(key:z) > 1) then (fire arrow)

        # Loop through projectiles and process them
        variable(sl, next)
        sl := firstchild(lookupslice(projectile layer))
        while (sl) do (
            next := nextsibling(sl)

            if (getslicelookup(sl) == sli:projectile) then (
                # Update position, and delete the slice when it's reached its target
                tracevalue(sl)
                if (projectile eachtick(sl) == false) then (
                    # Change to a 'splash' animation 
                    setslicelookup(sl, sli:splash)
                )
            ) elseif (getslicelookup(sl) == sli:splash) then (
                # Play a three-frame splash animation
                setspriteframe(sl, getspriteframe(sl) + 1)
                if (getspriteframe(sl) == 4) then (free slice(sl))
            )

            sl := next
        )

        wait
    )
end


See Also: other projectile scripts[edit]

  • General jump hero script to jump in any direction, or up or down a ledge
    • Simpler script to make the hero jump in an arc an arbitrary number of tiles in any direction: [1]
    • Simpler script to make the hero jump in an arc off a cliff in any direction (move one tile down plus one tile in given direction) [2]
    • Firing a projectile in an arc with a certain height and distance: using a for loop: (see [3] and [4]); or using a timer: [5]