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
ROTATEin"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 SHOW | Stop drawing it, or start again. It’s still there. |
STEP GHOST | Let things pass through it. |
STEP SOLID | Make 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
MUSICblock. Those are parts of a song. APATTERNon its own is always for sprites.
Patterns and behaviors move one sprite at a time. Next, sprites that move together, joined up.