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 SPRITEof their own, with nothing else after the!!.clear_pintakes 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.