More ways to move
VELOCITY, IMPULSE and ACCELERATION push a sprite about. That’s enough
for a ball, but plenty of things in a game move with somewhere in mind: after
the hero, round a planet, along a track. I have words for those too, and
they all go in a behavior.
Following
FOLLOW chases another sprite and keeps a distance behind it. Give each
piece of a snake the piece in front of it to follow, and you have a snake.
SCREEN 320, 240
MEMORYBUFFER "head", 14, 14
CIRCLE (7, 7), 6, 10, 10
END MEMORYBUFFER
MEMORYBUFFER "body", 14, 14
CIRCLE (7, 7), 5, 2, 2
END MEMORYBUFFER
BEHAVIOR "wander"
PATH [(40, 40), (280, 60), (240, 200), (60, 180), (40, 40)] !! absolute speed(2) loop
END BEHAVIOR
BEHAVIOR "tail"
FOLLOW PARAM("leader", 0), 12 !! speed(3)
END BEHAVIOR
ENABLE SPRITE 0
SET SPRITE 0 !! image("head") x(40) y(40) behavior("wander")
SHOW SPRITE 0
FOR n = 1 ... 8
ENABLE SPRITE n
SET SPRITE n !! image("body") x(40) y(40)
!! behavior("tail") behavior_params({leader: n - 1})
SHOW SPRITE n
NEXT
PAUSE
FOLLOW takes the sprite to follow and how far behind to stay, and
!! speed(3) is how fast it can catch up, in pixels a tick. The head’s
going at 2, so the body keeps up.
Paths
The head’s going round a PATH: a list of points to visit in order. Usually
the points are offsets from where the sprite starts, so the same path works
anywhere; with !! absolute they’re places on the screen. After !! too:
speed(n) | Pixels a tick. |
loop | Go back to the first point and round again. |
pingpong | Go back along the path the way it came, and forth again. |
seek_first(n) | Go to the first point first, at this speed. |
Without loop or pingpong, the path is done when it gets to its last
point, and the sprite stays there.
The head’s path ends on (40, 40), where it started, so each time round it
comes all the way home before it sets off again.
Orbits
ORBIT goes round and round at a distance. !! center((x, y)) goes round a
point; !! center_sprite(n) goes round a sprite, wherever that sprite goes.
So a moon can go round a planet that’s going round a sun.
SCREEN 320, 240
MEMORYBUFFER "sun", 24, 24
CIRCLE (12, 12), 11, 14, 14
END MEMORYBUFFER
MEMORYBUFFER "planet", 12, 12
CIRCLE (6, 6), 5, 9, 9
END MEMORYBUFFER
MEMORYBUFFER "moon", 6, 6
CIRCLE (3, 3), 2, 7, 7
END MEMORYBUFFER
BEHAVIOR "year"
ORBIT 80 !! center((160, 120)) speed(1)
END BEHAVIOR
BEHAVIOR "month"
ORBIT 20 !! center_sprite(1) speed(6) clockwise(FALSE)
END BEHAVIOR
ENABLE SPRITE 0
SET SPRITE 0 !! image("sun") x(160) y(120) origin((0.5, 0.5))
SHOW SPRITE 0
ENABLE SPRITE 1
SET SPRITE 1 !! image("planet") x(240) y(120) origin((0.5, 0.5)) behavior("year")
SHOW SPRITE 1
ENABLE SPRITE 2
SET SPRITE 2 !! image("moon") x(260) y(120) origin((0.5, 0.5)) behavior("month")
SHOW SPRITE 2
PAUSE
speed is in degrees a tick, so the planet takes 360 ticks, six seconds, to
go round once, and the moon goes round it nine times in that.
Sliding to a stop
FRICTION takes a share of a sprite’s speed away every tick: 0.03 is three
hundredths of it. With nothing pushing, it slows down and stops, like a puck
on ice.
Here the loop does the pushing. spritevelocityx and spritevelocityy say
how fast a sprite’s going right now, and !! velocity((x, y)) sets it: each
arrow key adds a little. Let go, and the friction wins.
SCREEN 320, 240
MEMORYBUFFER "puck", 16, 16
CIRCLE (8, 8), 7, 15, 8
END MEMORYBUFFER
BOUNDARY ADD "rink", (0, 0) -> (320, 240) !! solid enclosure
BEHAVIOR "ice"
FRICTION 0.03, 0.03
BOUNCE 0.8, 0.8
END BEHAVIOR
ENABLE SPRITE 0
SET SPRITE 0 !! image("puck") x(152) y(112) solid behavior("ice") velocity((4, 2))
SHOW SPRITE 0
DO
IF COOLDOWN(1)
vx = spritevelocityx(0)
vy = spritevelocityy(0)
IF keydown("Left") THEN vx = vx - 0.3
IF keydown("Right") THEN vx = vx + 0.3
IF keydown("Up") THEN vy = vy - 0.3
IF keydown("Down") THEN vy = vy + 0.3
SET SPRITE 0 !! velocity((vx, vy))
FRAME
CLS
LOCATE 1, 1
PRINT "speed", round(vx * 10) / 10, round(vy * 10) / 10
END FRAME
END IF
LOOP
Moving, and holding still
MOVE x, y moves a sprite by that much every tick. With !! absolute_x, the
x is where to hold it instead: every tick, I put it back at that x. absolute_y
does the same for y. Here’s a lift that rises a pixel a tick, and whatever x
it was placed at, it rides up at 145.
SCREEN 320, 240
NEWIMAGE 30, 6, 6, "lift"
BEHAVIOR "lift"
MOVE 145, -1 !! absolute_x
END BEHAVIOR
ENABLE SPRITE 0
SET SPRITE 0 !! image("lift") x(20) y(230) behavior("lift")
SHOW SPRITE 0
t = 0
DO WHILE t < 30
IF COOLDOWN(1) THEN t = t + 1
LOOP
PRINT "x is", spritex(0), "and y is going up:", spritey(0) < 230
x is 145 and y is going up: 1
Up off the ground
In 3D, a sprite has an elevation as well as x and y: how high it is off the
ground. IMPULSE, ACCELERATION, FRICTION and MOVE all take a third
number for it, ELEVATE takes it from one height to another over some
ticks, and GRAVITY pulls it back down to the ground, which is 0.
spriteelevation says how high it is. That’s for a sprite on a plane layer,
the tilted, Mode 7 kind of floor (SET LAYER 1 !! plane("perspective")),
where I draw it at its height.
On a flat screen, up and down is y, and the ground is something solid. Here a
ball hops on a solid floor: IMPULSE 0, -6 throws it up once,
ACCELERATION 0, 0.3 is its gravity, and the floor is what it lands on.
SCREEN 320, 240
MEMORYBUFFER "ball", 16, 16
CIRCLE (8, 8), 7, 12, 12
END MEMORYBUFFER
LINE (0, 184) -> (319, 239), 8 !! box fill
BOUNDARY ADD "floor", (0, 184) -> (320, 240) !! solid
BEHAVIOR "fall"
ACCELERATION 0, 0.3
END BEHAVIOR
BEHAVIOR "hop"
IMPULSE 0, -6
END BEHAVIOR
ENABLE SPRITE 0
SET SPRITE 0 !! image("ball") x(152) y(168) solid behavior("fall")
SET SPRITE 0 !! behavior("hop")
SHOW SPRITE 0
DO
IF COOLDOWN(1) AND keydown("Space") AND spritetouching(0, "TOP") = "floor"
SET SPRITE 0 !! remove_behavior("hop")
SET SPRITE 0 !! behavior("hop")
END IF
LOOP
Nothing in the loop moves the ball. It only lets Space hop again once the ball is standing on the floor. Click the screen and press Space.
Crash-tan’s Tip:
GRAVITYonly ever pulls on the elevation. For a platform game, where down is down the screen, useACCELERATION 0, 0.3.
A little different every time
A swarm where every bee flies the same way at the same speed doesn’t look like
a swarm. variance(base, low, high) in a behavior is a number that’s
different for each sprite it’s on: base, plus something from low to
high. varianceint is the same with whole numbers, and !! nonzero keeps
it off 0.
SCREEN 320, 240
NEWIMAGE 4, 4, 14, "bee"
BEHAVIOR "swarm"
ORBIT variance(60, -30, 30) !! center((160, 120)) speed(variance(2, -1, 1)) start_angle(variance(180, -180, 180))
END BEHAVIOR
FOR n = 0 ... 39
ENABLE SPRITE n
SET SPRITE n !! image("bee") x(160) y(120) behavior("swarm")
SHOW SPRITE n
NEXT
PAUSE
Forty sprites and one behavior, and no two of them fly alike: each one
circles at its own distance and its own speed, starting from its own place
on the circle. They all start in the middle, so without start_angle they’d
set off from the same side.
Faster ticks
I tick behaviors sixty times a second. SET BEHAVIOR RATE changes that for
every sprite at once, and everything a behavior does a tick, it does more or
less often. Half the rate is slow motion.
SCREEN 320, 240
NEWIMAGE 10, 10, 13, "dot"
BEHAVIOR "circle"
ORBIT PARAM("r", 60) !! center((160, 120)) speed(3)
END BEHAVIOR
FOR n = 0 ... 5
ENABLE SPRITE n
SET SPRITE n !! image("dot") x(180 + n * 15) y(120)
!! behavior("circle") behavior_params({r: 20 + n * 15})
SHOW SPRITE n
NEXT
slow = FALSE
DO
IF COOLDOWN(90)
slow = NOT slow
IF slow
SET BEHAVIOR RATE 20
ELSE
SET BEHAVIOR RATE 60
END IF
END IF
LOOP
Every second and a half it switches between full speed and a third of it.
COOLDOWN keeps its own time, sixty a second, whatever the behaviors are
doing.