Going further

Things you can skip until a game needs them. The tutorial covers everything required to build a complete game; this page collects the extras.

Sharing one image handle

If several drawables use the same image, resolve it once with Scene.image and pass the handle around:

def build(self):
    enemy = self.image("enemy.png")
    self.small = self.world.sprite_pool(enemy, count=12)
    self.boss  = self.world.sprite(enemy, frame=4)

A mistyped name is caught at build():

AssetNotFoundError: image 'enemyy.png' is not in myname.mygame

Fractional coordinates

Both axes accept fractional values. The renderer stores signed 8.8 fixed point, so ship.x += 0.25 moves a quarter of a column and accumulates properly across ticks — you do not need to keep your own float and round it.

Bringing your own tilemap buffer

Pass cells= when the game already owns the data, and the object is kept as it is, so writes to your buffer are what the renderer reads:

self.wall_data = bytearray(WALL_COLS * WALL_ROWS)
self.wall = world.tilemap("trench.png",
                             columns=WALL_COLS, rows=WALL_ROWS,
                             cells=self.wall_data)

The length is checked at the call. The buffer cannot be replaced or resized afterwards, because the renderer reads those bytes directly.

When you are filling in bulk and know every index is in range, index tilemap.cells directly and skip the bounds check:

self.ground.cells[row * self.ground.columns + col] = WINDOWS

Flipping tilemaps and labels

flip_x and flip_y mirror a tilemap’s or label’s whole visible area, leaving the source strip in its normal orientation. Why text at the top of the disc needs both is explained in Tilemaps and text.

Glyph tables in detail

A label maps characters to frames through a glyph table, resolved once at build() in this order:

  1. A glyphs= argument at the call site, for one-offs: hud.label("numerals.png", columns=5, glyphs="0123456789").

  2. A glyphs: entry in __images__.yaml, next to the strip it describes.

  3. CP437, the default, where frame = ord(ch).

Characters with no mapping, and spaces, become vs2.EMPTY_TILE, so the renderer skips them. If your font has an opaque background and you want a real space glyph, include " " in the table.

Font strips that pack a second colour at a fixed offset are reached with frame_offset:

self.status.write(3, 1, "ABXY", frame_offset=0x80)   # the red variant

The starfield

The drifting stars behind the display are off unless a scene asks for them:

class MyGame(vs2.Scene):
    starfield = True       # stars behind this scene

The flag is read each time the scene is entered, so a scene that leaves it False has no stars, whatever scene came before it. It belongs to the scene, not the game: a game with a title screen and a play screen sets it on each one that wants stars. Games written for the older sprite API always have the stars.

Reading idle time directly

vs2.controls.idle_ms is the milliseconds since any controller was touched. Most games want idle_timeout and on_idle() instead; see the reference.

Palette animation

Recolouring the loaded palette tints every sprite drawn from that palette group with one buffer write. See vs2.display.palettes in the reference.