Scenes

A scene is one screen of a game. Subclass vs2.Scene, build the display graph in build(), and drive it in update().

Lifecycle

build() runs on every entry, and when it returns the scene is sealed:

A scene starts building, becomes sealed when build returns, and is closed by pop, push or switch

The scene lifecycle.

While building, layers and drawables may be created. Once sealed, they may only be moved, re-framed, shown and hidden — any structural call raises vs2.SceneSealedError. Once closed, drawables are released and pending timers discarded, so a scene shown again always starts from a fresh build().

Persistent state can live in __init__, which runs once for the life of the object. Drawable handles cannot: rebuild them in build().

Transitions

All three are transitions. They queue and commit at the end of the tick, and all three return None, so return self.pop() reads as “handle this input, then stop”:

self.push(PauseMenu())        # suspend this scene, run another on top
self.pop()                    # resume the scene below, or exit the app
self.switch(GameOver(score))  # replace this scene outright

Once a transition is queued, no further game callbacks run that tick: if update() queued it the timer drain is skipped, and if a timer callback queued it the drain stops there. A timer coming due on the same tick as a game-over can therefore never poke a scene that has already decided to leave. Only one transition may be queued per tick; a second raises.

Class reference

class vs2.Scene[source]

Bases: Scene

One screen of a game: a display graph plus the logic that drives it.

Subclass it, build the graph in build(), and move it in update():

class MyGame(vs2.Scene):
    def build(self):
        self.world = self.layer("world", projection=vs2.TUNNEL)
        self.ship = self.world.sprite("ship.png", x=128, y=0)

    def update(self):
        self.ship.x += 0.5

The scene has three phases. During build() it is building and layers and drawables may be created. When build() returns it is sealed: update() and timers may move, re-frame and hide what exists, but any structural call raises SceneSealedError. When the scene stops showing it is closed, its drawables are released, and pending timers are discarded.

build() runs on every entry, so a scene shown a second time starts from a fresh graph. Persistent state — scores, progress, unlocked levels — can live in __init__, which runs once; drawable handles must be rebuilt.

idle_timeout = 30

Seconds without input from any controller before on_idle() fires. None disables the timeout. Defaults to 30 so an unattended console always drifts back to the launcher’s attract loop.

back_button = True

When true, Y or BACK pops the scene. Set it to False to claim those buttons for gameplay; the scene stays exitable through the idle timeout and the console’s home command.

starfield = False

When true, the drifting starfield is drawn behind this scene. Applied on entry; a scene that leaves it false has no stars, whatever came before. (Scenes written for the older sprite API always have them.)

asset_pack = None

Name of the asset pack to load, defaulting to the current app’s own. Shared and system scenes set this to borrow another pack.

build()[source]

Create this scene’s layers and drawables. Override this.

Runs on every entry to the scene. When it returns the scene is sealed, so this is the only place layer() and the Layer factories may be called.

update()[source]

Advance the game by one tick. Override this.

Called once per rotation of the display. It may move, re-frame, show and hide the drawables built in build(), but may not create more.

teardown()[source]

Clean up when the scene stops showing. Optional.

Saving a score or cancelling external work belongs here. Timers and drawables are released for you, so most scenes never need it.

image(value)[source]

Resolve an image name to an Image handle.

Handles are cached for the life of the scene, so resolving the same name twice returns the same object. Passing an Image returns it unchanged, which lets factories accept either form.

Parameters:

value – An image name such as "ship.png", or an Image.

Returns:

Image – The resolved handle.

Raises:

AssetNotFoundError – If the name is not in the scene’s asset pack.

layer(name=None, projection=1, visible=True)[source]

Create a layer. Only callable from build().

Layers paint bottom to top in the order they are created, so create the background first and the HUD last.

Parameters:
  • name – A label used in resource-limit messages. Worth setting.

  • projection – TUNNEL, HUD or FULLSCREEN. Decides how the layer maps Y to LEDs.

  • visible – Whether the layer draws at all.

Returns:

Layer – The new layer, ready to create drawables on.

Raises:
on_enter()[source]
on_exit()[source]
call_later(delay, callback, *args, **kwargs)[source]

Schedule a one-shot callback delay milliseconds from now.

Extra positional and keyword arguments are stored with the timer and passed to the callback when it fires:

self.call_later(1500, self.respawn)
self.call_later(500, self.spawn_wave, wave, boss=True)

Timers live only as long as the scene is showing. Pending callbacks are discarded the moment it stops showing — popped, or suspended under a push() — and a scene shown again starts from a fresh build() with no timers.

Scheduling is meant to be infrequent: menus, respawn delays, wave timers. Do not call this every tick.

Raises:

SceneSealedError – If the scene is not building or running.

push(scene)[source]

Suspend this scene and run scene on top of it.

This scene’s timers are discarded and its drawables released; when the pushed scene pops, this one is entered again from a fresh build().

pop()[source]

Leave this scene, resuming the one below, or exit to the launcher.

Returns None, so return self.pop() reads as “handle this input, then stop”. No V2 scene raises StopIteration.

switch(scene)[source]

Replace this scene with scene, without growing the stack.

Music and base-hardware state carry over, since both follow the app rather than the scene.

on_idle()[source]

Called after idle_timeout seconds without input.

The default pops the scene, so an unattended console walks back out to the launcher one screen at a time. Override it for an attract loop:

def on_idle(self):
    self.push(AttractSlideshow())
scene_step()[source]