Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.
loopGo back to the first point and round again.
pingpongGo 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: GRAVITY only ever pulls on the elevation. For a platform game, where down is down the screen, use ACCELERATION 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.