Scenes
A game is more than the part where you play it. There’s a title screen first, then the level, then a game over screen, and then the title again. Each part makes sprites, starts behaviors and draws things of its own, and none of that should still be lying around when the next part starts.
A SCENE block takes care of that. I remember the game world as it is when
the block starts, and when it ends, I clear away everything the scene made
and put the world back the way it was.
In and out again
SCREEN 320, 240
NEWIMAGE 8, 8, 15, "dot"
ENABLE SPRITE 0
SET SPRITE 0 !! image("dot") x(10) y(10)
SHOW SPRITE 0
score = 0
SCENE "bonus"
SET SPRITE 0 !! x(200)
extra = reserve_sprite()
SET SPRITE extra !! image("dot") x(20) y(20)
SHOW SPRITE extra
score = 50
PRINT "in the scene: sprite 0 is at x", spritex(0), "and there's a sprite", extra
END SCENE
PRINT "after it: sprite 0 is at x", spritex(0)
PRINT "is sprite", extra, "still there?", spriteenabled(extra)
PRINT "score", score
in the scene: sprite 0 is at x 200 and there's a sprite 1
after it: sprite 0 is at x 10
is sprite 1 still there? 0
score 50
Sprite 0 was there before the scene, so it’s part of the world: the scene
moved it, and at END SCENE it went back. Sprite 1 was made in the scene,
so it went with it, and its number is free for the next one. Your
variables are yours and I leave them alone, which is how the score gets out
of a scene to the next one.
What a scene cleans up is everything it started: the sprites, layers and
boundaries it made, the behaviors it put on sprites, the shaders, and any
CALL RENDER, thread or timer. Palette colors it changed get their old
values back.
Crash-tan’s Tip:
SCENE "name" !! fastonly clears away what the scene made, without putting back the world as it was. It’s quicker, for the times when nothing from before needs to survive.
A scene ends when its block does
A scene is the code between SCENE and END SCENE, so it lasts as long as
that code runs. A title screen is a scene with a loop in it that waits for
a key. A level is a scene with the game’s loop in it, and the level is over
when the loop is.
That also makes a scene fit nicely in a function, one for each part of the
game, and leaving the function by RETURN ends the scene too:
SCREEN 320, 240
NEWIMAGE 8, 8, 14, "dot"
FUNCTION level()
SCENE "level"
star = reserve_sprite()
SET SPRITE star !! image("dot") x(160) y(120)
SHOW SPRITE star
RETURN star
END SCENE
END FUNCTION
used = level()
PRINT "the level used sprite", used
PRINT "and now it's", spriteenabled(used)
the level used sprite 0
and now it's 0
Title, play, game over
Here’s a whole small game in three scenes. Rocks fall from the sky, and you dodge them for as long as you can. The main loop at the bottom is the whole game in four lines: title, play, game over, and round again.
SCREEN 320, 240
RANDOMIZE
LOADIMG "hero.png", "crashtan"
LOADIMG "city.png", "city"
MEMORYBUFFER "rock", 16, 16
CIRCLE (8, 8), 7, 6, 6
END MEMORYBUFFER
ATOMIC score = 0
ATOMIC hit = FALSE
best = 0
// Wait for Space to be let go of, and then pressed.
FUNCTION wait_for_space()
released = FALSE
DO
IF COOLDOWN(1)
IF NOT keydown("Space") THEN released = TRUE
IF released AND keydown("Space") THEN RETURN
END IF
LOOP
END FUNCTION
// --- The title screen ---------------------------------------------------
BEHAVIOR "bob"
OSCILLATE 0, 8 !! speed(2)
END BEHAVIOR
FUNCTION show_title()
SCENE "title"
FRAME
CLS
PUTIMAGE (32, 0), "city"
LOCATE 4, 16
PRINT "D O D G E"
LOCATE 25, 10
PRINT "press Space to start"
LOCATE 27, 14
PRINT "best:", best
END FRAME
mascot = reserve_sprite()
SET SPRITE mascot !! image("crashtan") x(140) y(100) behavior("bob")
SHOW SPRITE mascot
wait_for_space()
END SCENE
END FUNCTION
// --- Playing ----------------------------------------------------------
BEHAVIOR "fall"
VELOCITY 0, PARAM("speed", 2)
ONCOLLISION
IF other_tag = "player" THEN hit = TRUE
IF other_tag = "floor" THEN DISABLE SPRITE me
END ONCOLLISION
END BEHAVIOR
FUNCTION draw_play()
FRAME
CLS
PUTIMAGE (32, 0), "city"
LOCATE 1, 1
PRINT "score", score
END FRAME
END FUNCTION
FUNCTION play_level()
SCENE "play"
score = 0
hit = FALSE
BOUNDARY ADD "floor", (0, 260) -> (320, 270)
CALL RENDER draw_play, "hud"
player = reserve_sprite()
x = 140
SET SPRITE player !! image("crashtan") x(x) y(200) tag("player")
SHOW SPRITE player
DO WHILE NOT hit
IF COOLDOWN(1)
IF keydown("Left") AND x > 0 THEN x = x - 3
IF keydown("Right") AND x < 280 THEN x = x + 3
SET SPRITE player !! x(x)
score = score + 1
END IF
IF COOLDOWN(20)
rock = reserve_sprite()
IF rock >= 0
SET SPRITE rock !! image("rock") x(rnd * 304) y(-16)
!! behavior("fall") behavior_params({speed: 2 + score / 600})
SHOW SPRITE rock
END IF
END IF
LOOP
END SCENE
END FUNCTION
// --- Game over ------------------------------------------------------------
FUNCTION show_game_over()
IF score > best THEN best = score
SCENE "game over"
FRAME
CLS 4
LOCATE 12, 16
PRINT "GAME OVER"
LOCATE 14, 14
PRINT "you scored", score
LOCATE 25, 9
PRINT "press Space for the title"
END FRAME
wait_for_space()
END SCENE
END FUNCTION
// --- The game ---------------------------------------------------------------
DO
show_title()
play_level()
show_game_over()
LOOP
Click the screen, press Space, and use the arrow keys.
Look at what doesn’t happen. play_level never takes the rocks away when you’re
hit, or turns off its CALL RENDER, or removes the floor, and the title
never hides its bobbing hero. Every one of those was made inside a
scene, so END SCENE cleared it away. The next scene starts with nothing
left over from the last, every time round.
score and best are ordinary variables, so they carry on from one scene
to the next: play_level sets the score, show_game_over shows it and
keeps the best, and show_title shows the best.
Crash-tan’s Tip: A scene puts the world back as it was when the scene started, so make the things that belong to the whole game, like images and behaviors, before the first scene. Anything that belongs to one part of the game goes inside that part’s scene.
Make it yours
- A transition. Start the game over scene with a
CLSand an effect from Shaders and transitions, and wait fortransitionto be""before you draw on it. - A flash. When the hero is hit, give it the
"hurt"behavior from the same lesson, and wait a moment before the scene ends. - Another level. After a minute of dodging, end
play_leveland go to a second scene where the rocks fall faster.