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

Patterns

A behavior is everything a sprite does at once, every tick: move this way, spin, fade, all together, for as long as it’s on. That’s perfect for a spinning coin. But a bad guy in a shooter has a plan: fly in, stop, fire, fly away. Those happen one after another, and that’s a pattern.

One step after another

A PATTERN is a list of steps, and I go down it in order. STEP with a behavior’s name runs that behavior until it’s done, or with !! duration(n), for n ticks. Then I move on to the next step.

SCREEN 320, 240
NEWIMAGE 12, 12, 10, "box"

BEHAVIOR "right"
    VELOCITY 2, 0
END BEHAVIOR
BEHAVIOR "down"
    VELOCITY 0, 2
END BEHAVIOR

PATTERN "corner"
    STEP "right" !! duration(60)
    STEP "down" !! duration(40)
END PATTERN

ENABLE SPRITE 0
SET SPRITE 0 !! image("box") x(20) y(20) pattern("corner")
SHOW SPRITE 0
PAUSE

The box goes right for a second, then down for two thirds of one, then stops, because the pattern’s run out of steps. A sprite gets its pattern with pattern(...), the way it gets a behavior with behavior(...).

With !! loop after its name, a pattern starts again from its first step when it gets to the end. Try PATTERN "corner" !! loop, and add two more steps to bring the box back round.

A bad guy with a plan

The other STEP words don’t run a behavior: they do one thing to the sprite, there and then. STEP BEVENT fires an event, which is how a pattern tells your program something’s happened. STEP DISABLE takes the sprite out of the game, and gives its number back.

Here’s that plan. A ship flies down the screen to its place, sways there for a second, fires, sways a little more, flies off the top and is gone.

SCREEN 320, 240
RANDOMIZE
MEMORYBUFFER "ship", 20, 14
    POLYGON [(0, 0), (20, 0), (10, 14)], 12, 4
END MEMORYBUFFER
MEMORYBUFFER "shot", 4, 8
    LINE (0, 0) -> (3, 7), 14 !! box fill
END MEMORYBUFFER

BEHAVIOR "arrive"
    PATH [(0, 0), (0, 90)] !! speed(3)
END BEHAVIOR
BEHAVIOR "sway"
    OSCILLATE 8, 0 !! speed(3)
END BEHAVIOR
BEHAVIOR "leave"
    VELOCITY 0, -4
END BEHAVIOR
BEHAVIOR "falling"
    VELOCITY 0, 3
END BEHAVIOR

PATTERN "attack"
    STEP "arrive"
    STEP "sway" !! duration(60)
    STEP BEVENT "fire"
    STEP "sway" !! duration(30)
    STEP "leave" !! duration(40)
    STEP DISABLE
END PATTERN

FUNCTION on_fire(ship)
    EMITPARTICLE (spritex(ship, 0.5), spritey(ship, 1)), "shot", "shot", "falling", 80
END FUNCTION
CALL EVENT on_fire, "fire"

DO
    IF COOLDOWN(45)
        id = reserve_sprite()
        IF id >= 0
            SET SPRITE id !! image("ship") x(20 + rnd * 260) y(-20)
                !! tag("enemy") pattern("attack")
            SHOW SPRITE id
        END IF
    END IF
LOOP

PATH with no loop is done once it’s been to its last point, so the "arrive" step lasts exactly as long as the trip. The event comes to on_fire with the sprite that fired it, so the shot starts under the right ship.

Crash-tan’s Tip: A pattern runs one step at a time, but a step’s behavior can do as much at once as any behavior. Put ROTATE in "leave" and the ship spins as it goes.

Waiting for a signal

STEP WAITFOR stands still until an event fires. Fire it with EVENT from your program, and every sprite waiting for it goes at once. !! timeout(n) gives up after n ticks, so with an event nobody ever fires, it’s a plain wait.

STEP GOTOPATTERN carries on with another pattern, from its first step, and STEP FADE fades the sprite out: STEP FADE 2 takes about half a second, and a bigger number is faster.

SCREEN 320, 240
NEWIMAGE 14, 14, 11, "drone"

BEHAVIOR "hover"
    OSCILLATE 0, 4 !! speed(4)
END BEHAVIOR
BEHAVIOR "dive"
    ACCELERATION 0, 0.15
END BEHAVIOR

PATTERN "wait"
    STEP WAITFOR "attack"
    STEP GOTOPATTERN "dive"
END PATTERN
PATTERN "dive"
    STEP "dive" !! duration(50)
    STEP FADE 2
    STEP DISABLE
END PATTERN

FOR n = 0 ... 7
    ENABLE SPRITE n
    SET SPRITE n !! image("drone") x(30 + n * 35) y(40)
        !! behavior("hover") pattern("wait")
    SHOW SPRITE n
NEXT

ticks = 0
DO
    IF COOLDOWN(1)
        ticks = ticks + 1
        IF ticks = 60 THEN EVENT "attack"
        FRAME
            CLS
            LOCATE 1, 1
            IF spriteenabled(0) THEN PRINT "sprite 0 is running", spritepattern(0)
        END FRAME
    END IF
LOOP

The drones hover from the start: behavior("hover") is on the sprite itself, and a sprite’s behaviors go on whatever step its pattern’s on. After a second the program fires "attack", and the whole row dives together.

spritepattern(n) gives the name of the pattern a sprite’s running, or "". haspattern(n) asks whether it’s running one at all, and haspattern(n, "dive") whether it’s running that one.

Changing what a sprite is

The last few steps change how the sprite takes part in the game, part way through its plan:

STEP HIDE, STEP SHOWStop drawing it, or start again. It’s still there.
STEP GHOSTLet things pass through it.
STEP SOLIDMake it solid again.
STEP RETAG "name"Give it another tag, the one collisions report.

Here’s a ghost that drifts across the screen, and every so often vanishes, goes on drifting unseen with nothing able to touch it, and comes back.

SCREEN 320, 240
MEMORYBUFFER "ghost", 16, 18
    CIRCLE (8, 8), 7, 15, 15
    LINE (1, 8) -> (15, 17), 15 !! box fill
    PSET (5, 7), 0
    PSET (11, 7), 0
END MEMORYBUFFER

BEHAVIOR "drift"
    VELOCITY 1, 0
END BEHAVIOR

PATTERN "haunt" !! loop
    STEP "drift" !! duration(60)
    STEP GHOST
    STEP RETAG "faded"
    STEP HIDE
    STEP "drift" !! duration(40)
    STEP SHOW
    STEP SOLID
    STEP RETAG "ghost"
END PATTERN

ENABLE SPRITE 0
SET SPRITE 0 !! image("ghost") x(0) y(110) tag("ghost") pattern("haunt")
SHOW SPRITE 0
DO
    IF COOLDOWN(1)
        FRAME
            CLS
            LOCATE 1, 1
            PRINT "tag:", spritetag(0), "  shown:", spritevisible(0)
        END FRAME
    END IF
LOOP

A bullet’s ONCOLLISION that looks at other_tag would know not to count a hit on a "faded" ghost.

Keeping them

A pattern can be saved to a file with SAVE PATTERN, and LOAD PATTERN brings back every pattern in one, ready to use. So can a behavior, with SAVE BEHAVIOR and LOAD BEHAVIOR: a level editor can write the plans, and the game can read them.

SCREEN 320, 240
BEHAVIOR "drift"
    VELOCITY 1, 0
END BEHAVIOR
PATTERN "patrol" !! loop
    STEP "drift" !! duration(60)
END PATTERN
SAVE PATTERN "patrol", "patrol.json"
PRINT "saved:", fileexists("patrol.json")

LOAD PATTERN "patrol.json"
NEWIMAGE 8, 8, 15, "dot"
ENABLE SPRITE 0
SET SPRITE 0 !! image("dot") x(20) y(100) pattern("patrol")
PRINT "running:", spritepattern(0)
saved: 1
running: patrol

Crash-tan’s Tip: The music has patterns too, inside a MUSIC block. Those are parts of a song. A PATTERN on its own is always for sprites.

Patterns and behaviors move one sprite at a time. Next, sprites that move together, joined up.