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

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" !! fast only 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 CLS and an effect from Shaders and transitions, and wait for transition to 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_level and go to a second scene where the rocks fall faster.