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

Sprites joined together

Some things in a game are made of parts: a tank and its turret, a windmill and its sails, an arm with a hand on the end. Each part is a sprite, and I can join them up, so that moving one moves the lot. A set of sprites joined like that is a rig.

Carried along

!! parent(n) puts a sprite on another one. From then on, wherever the parent goes, the child goes too, keeping the same distance from it. The child can still have behaviors of its own.

SCREEN 320, 240
LOADIMG "ball.png", "ball"
MEMORYBUFFER "flag", 12, 8
    LINE (0, 0) -> (11, 7), 12 !! box fill
END MEMORYBUFFER

BEHAVIOR "roll"
    PATH [(0, 0), (200, 0)] !! speed(2) pingpong
END BEHAVIOR
BEHAVIOR "wave"
    OSCILLATE 0, 3 !! speed(6)
END BEHAVIOR

ENABLE SPRITE 0
SET SPRITE 0 !! image("ball") x(40) y(130) scale_to((32, 32)) behavior("roll")
SHOW SPRITE 0
ENABLE SPRITE 1
SET SPRITE 1 !! image("flag") x(50) y(110) parent(0) behavior("wave")
SHOW SPRITE 1
PAUSE

The ball rolls back and forth, and the flag rides on top, waving as it goes. ball.png is 128 pixels across, so scale_to((32, 32)) shows it at 32.

A plain child only follows its parent about. With rigid as well, it turns with the parent too, going round it when the parent spins:

SCREEN 320, 240
LOADIMG "ball.png", "ball"
MEMORYBUFFER "sail", 50, 10
    LINE (0, 0) -> (49, 9), 15 !! box fill
END MEMORYBUFFER

BEHAVIOR "spin"
    ROTATE 360, 240 !! origin((0.5, 0.5)) loop
END BEHAVIOR

ENABLE SPRITE 0
SET SPRITE 0 !! image("ball") x(160) y(120)
    !! origin((0.5, 0.5)) scale_to((24, 24))
    !! behavior("spin")
SHOW SPRITE 0
FOR n = 1 ... 4
    angle = (n - 1) * 90
    ENABLE SPRITE n
    SET SPRITE n !! image("sail") origin((0.5, 0.5)) rotate(angle)
        !! x(160 + cos(angle * pi / 180) * 40) y(120 + sin(angle * pi / 180) * 40)
        !! parent(0) rigid
    SHOW SPRITE n
NEXT
PAUSE

Only the hub has a behavior. The four sails are placed around it once, and from then on they go round because it does.

Pulled toward a point

A pin pulls a sprite toward a place every tick. pin_target((x, y)) is where, and pin_mix is how hard, from 0 to 1: at 1 it’s there at once, and at 0.1 it goes a tenth of the way each tick, so it eases in and slows as it arrives.

SCREEN 320, 240
LOADIMG "ball.png", "ball"
ENABLE SPRITE 0
SET SPRITE 0 !! image("ball") x(160) y(120) origin((0.5, 0.5)) scale_to((20, 20))
SHOW SPRITE 0
DO
    IF COOLDOWN(1) THEN SET SPRITE 0 !! pin_target((mousex, mousey)) pin_mix(0.1)
LOOP

Move the mouse over the screen and the ball glides after it.

Crash-tan’s Tip: A pin, a limit and IK each go in a SET SPRITE of their own, with nothing else after the !!. clear_pin takes a pin off again.

Kept within limits

A limit keeps a child within a range of its parent. limit_x(-50 ... 50) lets it go fifty pixels either side of its parent and no further, and limit_y does the same up and down. Here a knob swings a hundred pixels each way, and the limit stops it at the ends of its rail.

SCREEN 320, 240
LOADIMG "ball.png", "ball"
NEWIMAGE 120, 4, 8, "rail"

BEHAVIOR "swing"
    OSCILLATE 100, 0 !! speed(3)
END BEHAVIOR

ENABLE SPRITE 0
SET SPRITE 0 !! image("rail") x(160) y(120) origin((0.5, 0.5))
SHOW SPRITE 0
ENABLE SPRITE 1
SET SPRITE 1 !! image("ball") x(160) y(120)
    !! origin((0.5, 0.5)) scale_to((16, 16)) parent(0)
    !! behavior("swing")
SET SPRITE 1 !! limit_x(-52 ... 52)
SHOW SPRITE 1
PAUSE

limit_axis("x") keeps a child level with its parent, so it can only slide from side to side.

Reaching

IK, inverse kinematics, works the other way round from a parent. You say where the end of a chain should be, and I bend every joint of it to get there. ik_target((x, y)) goes on the last sprite of a chain, and ik_length says how many sprites back up the chain I’m allowed to bend.

Here’s a tentacle of nine balls, each a rigid child of the one before. The first stays where it is, and the tip reaches for the mouse. The lines between the balls are drawn into a MEMORYBUFFER every frame, and go on the bottom layer, under the sprites.

SCREEN 320, 240
LOADIMG "ball.png", "ball"
links = 8

FOR n = 0 ... links
    ENABLE SPRITE n
    SET SPRITE n !! image("ball") x(160 + n * 18) y(120)
        !! origin((0.5, 0.5)) scale_to((14, 14))
    IF n > 0 THEN SET SPRITE n !! parent(n - 1) rigid
    SHOW SPRITE n
NEXT

DO
    IF COOLDOWN(1)
        SET SPRITE links !! ik_target((mousex, mousey)) ik_length(links)
        MEMORYBUFFER "bones"
            FOR n = 1 ... links
                LINE (spritex(n - 1, 0.5), spritey(n - 1, 0.5)) -> (spritex(n, 0.5), spritey(n, 0.5)), 8
            NEXT
        END MEMORYBUFFER
        FRAME
            CLS
            PUTIMAGE (0, 0), "bones"
        END FRAME
    END IF
LOOP

When the mouse is further away than the tentacle is long, it stretches out straight toward it, as far as it reaches.

Saving a rig

SAVE RIG writes a sprite and everything joined to it to a file: their images, places, behaviors and patterns. LOAD RIG builds it again, and with !! onto(n) it builds it on sprite n, with new numbers for the parts. Make one guard, save it, and you can have as many as you like.

SCREEN 320, 240
LOADIMG "ball.png", "ball"
MEMORYBUFFER "helmet", 16, 8
    LINE (0, 0) -> (15, 7), 4 !! box fill
END MEMORYBUFFER

ENABLE SPRITE 0
SET SPRITE 0 !! image("ball") x(60) y(120) scale_to((24, 24))
SHOW SPRITE 0
ENABLE SPRITE 1
SET SPRITE 1 !! image("helmet") x(64) y(112) parent(0) tag("helmet")
SHOW SPRITE 1
SAVE RIG 0, "guard.json"

FOR n = 1 ... 3
    body = reserve_sprite()
    SET SPRITE body !! image("ball") x(60 + n * 60) y(120) scale_to((24, 24))
    SHOW SPRITE body
    LOAD RIG "guard.json" !! onto(body)
NEXT
PRINT "helmets:", spritecount("helmet")
helmets: 4

Four: the first guard’s, and one for each guard loaded onto a new body.

That’s every way I know to join sprites up. Next, all the other ways I know to move them.