Scripts:Fire projectile

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]