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

Animation and particles

Back in Images and sprites, a sprite changed its frame because the loop told it to. That’s fine for one light blinking, but a game is full of things that animate: a walk, a spinning coin, a flame. A behavior can step through the frames for me, so your loop never has to count them.

A sheet of frames

explosion.png is in every game’s folder. It’s a sheet of 32 × 32 frames, so it loads with !! spritesheet((32, 32)), and framecount says how many frames that made.

SCREEN 320, 240
LOADIMG "explosion.png", "boom" !! spritesheet((32, 32))
PRINT "the explosion has", framecount("boom"), "frames"
the explosion has 16 frames

Playing them

ANIMATE goes in a behavior. It takes the image, the frames to play in order, and after !!, how many frames a second to show them at. Frames 0 ... 15 is all sixteen, and it starts again from the first when it gets to the end.

SCREEN 320, 240
LOADIMG "explosion.png", "boom" !! spritesheet((32, 32))

BEHAVIOR "burning"
    ANIMATE "boom", 0 ... 15 !! framerate(12)
END BEHAVIOR

ENABLE SPRITE 0
SET SPRITE 0 !! image("boom") frame(0) x(160) y(120)
    !! origin((0.5, 0.5)) scale((3, 3))
    !! behavior("burning")
SHOW SPRITE 0
PAUSE

Try framerate(30) for a faster fire, or [4, 5, 6, 7] to play only the middle of it. The frames don’t have to be in order, either: [0, 1, 2, 1] goes there and back.

Standing and running

A hero has more than one animation: one for standing about and one for running, each a sheet of its own. hero_idle.png and hero_run.png are sheets of 40 × 40 frames. Give each its own behavior, and when the hero starts or stops running, take one behavior off and put the other on. ANIMATE changes the sprite’s picture to its own sheet as it starts, so the sprite always shows the one that’s playing.

SCREEN 320, 240
LOADIMG "hero_idle.png", "idle" !! spritesheet((40, 40))
LOADIMG "hero_run.png", "run" !! spritesheet((40, 40))

BEHAVIOR "standing"
    ANIMATE "idle" !! framerate(6)
END BEHAVIOR
BEHAVIOR "running"
    ANIMATE "run" !! framerate(12)
END BEHAVIOR

x = 120
ENABLE SPRITE 0
SET SPRITE 0 !! image("idle") frame(0) x(x) y(80) scale((2, 2))
    !! behavior("standing")
SHOW SPRITE 0

running = FALSE
DO
    IF COOLDOWN(1)
        way = keydown("Right") - keydown("Left")
        x = x + way * 2
        IF x < 0 THEN x = 0
        IF x > 240 THEN x = 240
        SET SPRITE 0 !! x(x)
        IF way < 0 THEN SET SPRITE 0 !! flip_x
        IF way > 0 THEN SET SPRITE 0 !! flip_x(FALSE)

        IF (way <> 0) <> running
            running = way <> 0
            IF running
                SET SPRITE 0 !! remove_behavior("standing")
                SET SPRITE 0 !! behavior("running")
            ELSE
                SET SPRITE 0 !! remove_behavior("running")
                SET SPRITE 0 !! behavior("standing")
            END IF
        END IF
    END IF
LOOP

Click the screen and hold an arrow key. With no frames listed, ANIMATE plays every frame in the sheet. The loop only swaps the behaviors when running starts or stops: giving the sprite "running" again every tick would start the run over from its first frame, every tick.

scale((2, 2)) shows each pixel of the art as two by two, and that’s the way to make pixel art bigger: by a whole number. Half sizes and odd sizes smear the pixels.

Once, and gone

An explosion shouldn’t go round and round. !! noloop plays the frames once and holds the last one. Then the sprite has to go, and that’s what a particle is for.

EMITPARTICLE makes a sprite that looks after itself: where it starts, the image, a tag, the behavior that drives it, and how many ticks it lives. When its time’s up it’s gone, and its number goes back to me. You don’t ENABLE, SHOW or DISABLE anything.

SCREEN 320, 240
RANDOMIZE
LOADIMG "explosion.png", "boom" !! spritesheet((32, 32))
LOADWAV "explosion.wav", "bang"

BEHAVIOR "explode"
    ANIMATE "boom", 0 ... 15 !! framerate(30) noloop
END BEHAVIOR

DO
    IF COOLDOWN(40)
        EMITPARTICLE (20 + rnd * 250, 20 + rnd * 180), "boom", "boom", "explode", 32 !! scale((2, 2))
        PLAYWAV "bang", 0, 0.3
    END IF
LOOP

Sixteen frames at thirty a second take 32 ticks, so each explosion lives exactly as long as its animation. The !! scale((2, 2)) after it is the same scale a sprite takes: a particle takes most of what SET SPRITE does.

Sparks

A particle can be driven by any behavior, and one behavior can send each particle its own way: give it settings with params, the way behavior_params does for a sprite. Here’s a fountain of sparks, each one thrown up at its own angle, pulled down, and fading as it goes.

SCREEN 320, 240
RANDOMIZE
MEMORYBUFFER "spark", 4, 4
    LINE (0, 0) -> (3, 3), 14 !! box fill
END MEMORYBUFFER

BEHAVIOR "spark"
    IMPULSE PARAM("vx", 0), PARAM("vy", -3)
    ACCELERATION 0, 0.1
    ALPHA 1, 0, 50
END BEHAVIOR

DO
    IF COOLDOWN(2)
        vx = rnd * 3 - 1.5
        vy = -2 - rnd * 3
        EMITPARTICLE (160, 200), "spark", "spark", "spark", 50 !! params({vx: vx, vy: vy})
    END IF
    IF COOLDOWN(30)
        FRAME
            CLS
            LOCATE 1, 1
            PRINT "sparks in the air:", spritecount("spark")
        END FRAME
    END IF
LOOP

A new spark every two ticks, each living fifty: there are never more than twenty-five at once, and I tidy up every one of them.

Crash-tan’s Tip: EMITPARTICLE is also a function, emitparticle, that gives back the particle’s sprite number, in case you want to do something more to it.

Two more things a behavior can do to how a sprite looks. BLINK 6 hides and shows it every six ticks, the classic “I just got hit, I can’t be hurt for a moment” flicker. SHAKE 2 jiggles it up to two pixels either way. Put them in a behavior of their own, and add it and take it away again with behavior and remove_behavior.

SCREEN 320, 240
LOADIMG "hero.png", "tan"

BEHAVIOR "hurt"
    BLINK 6
    SHAKE 2
END BEHAVIOR

ENABLE SPRITE 0
SET SPRITE 0 !! image("tan") x(160) y(120) origin((0.5, 0.5)) behavior("hurt")
SHOW SPRITE 0

hurt = 60
DO
    IF COOLDOWN(1)
        IF keydown("Space") AND hurt = 0
            SET SPRITE 0 !! behavior("hurt")
            hurt = 60
        END IF
        IF hurt > 0
            hurt = hurt - 1
            IF hurt = 0 THEN SET SPRITE 0 !! remove_behavior("hurt")
        END IF
    END IF
LOOP

The hero starts out hurt. Once they’ve settled, click the screen and press Space, and they flicker and tremble for another second.

That’s a lot of moving and looking that the loop doesn’t have to think about. Next, a way to tell a sprite to do one thing, then another, then another.