Pixel Runner

Clear Code’s Pygame endless runner, with Timer signals, flipbook sprites and synthesised audio.

▶ Run in browser

Upstream: https://github.com/clear-code-projects/UltimatePygameIntro

Licence: this port's own code is offered under MIT, not the SimVX Examples Licence the rest of the gallery carries. See ATTRIBUTION.md for the upstream it re-implements, the terms of anything it bundles, and the credit each one requires.

Ports live in the repository only, not in the simvx-examples distribution, because each is a derivative work licensed individually against the game it re-implements. Read it with git clone https://git.simvx.com/simvx/simvx.

Tags: port tier-0

Pixel Runner: SimVX Port

Faithful port of Clear Code’s Ultimate Pygame Intro endless runner to SimVX.

What it demonstrates

The whole game is one root node (nodes/runner.py) holding two states, the title screen and the run, exactly as the upstream tutorial does. Along the way it shows how the pygame idioms the tutorial teaches map onto engine features:

Pygame

SimVX

while True: + pygame.event.get()

on_update(dt) + Input.just_pressed

pygame.sprite.Group + .update()

child nodes + tree.group("obstacles")

pygame.time.set_timer(USEREVENT)

a Timer child node and its timeout signal

swapping self.image every frame

AnimatedSprite2D.from_frames (one strip atlas flipbook)

screen.blit(sky_surface, (0, 0))

Sprite2D background nodes

screen.blit(score_surf, ...)

Text2D labels with align="centre"

pygame.sprite.spritecollide

Rect2.intersects between node rects

pygame.mixer.Sound("audio/jump.mp3")

AudioPlayer over a synthesised AudioClip

a fixed 800x400 surface

viewport_size, relaid on the tree’s screen_resized signal

pygame.key.get_pressed() per binding

one named action per verb, carrying key, tap and pad

Run

simvx examples run ports_ultimate_pygame_intro           # interactive
simvx examples run ports_ultimate_pygame_intro --test    # headless capture

Controls

Input

Action

SPACE / ↑ / W / tap anywhere / pad A

Jump

SPACE / Enter / tap anywhere / pad A, Start

Start / restart after death

Esc / pad Back

Quit

The game has one verb, so the whole screen is the jump button on a phone rather than a drawn on-screen pad, which would cover part of a short play area to draw a single control. nodes/runner.py declares all three actions in Runner.input_context, so they are in force while the runner is mounted: nothing is registered from main(), where InputMap has no tree yet.

Tests

uv run --package simvx-core pytest examples/ports/ultimate_pygame_intro/tests

Web export

simvx export web main.py -o pixel_runner.html

Asset licence

No upstream assets are bundled (the upstream repository declares no licence). Character and enemy sprites and the ground tiles are from Kenney’s CC0 Platformer Art Complete Pack (https://kenney.nl / https://opengameart.org/content/platformer-art-complete-pack-often-updated), resized to the original port geometry. The sky is generated, and all audio is synthesised at load time (nodes/audio.py): the music loop through the engine’s AudioSynth, the jump chirp as a swept sine fed to AudioClip.from_pcm. See ATTRIBUTION.md and LICENSE (MIT).

Source files

File

Summary

Lines

main.py

Pixel Runner: Clear Code’s Pygame endless runner, with Timer signals, flipbook sprites and synthesised audio.

56

__init__.py

Pixel Runner: SimVX port package.

1

nodes/__init__.py

Pixel Runner port: node modules.

1

nodes/assets.py

Where the port’s bundled art lives.

10

nodes/audio.py

Procedural audio: built on the engine’s AudioSynth API.

103

nodes/obstacle.py

Obstacle nodes: replaces Pygame Obstacle(sprite.Sprite) for fly + snail.

80

nodes/player.py

Player node: replaces Pygame Player(sprite.Sprite).

141

nodes/runner.py

Pixel Runner: root scene replacing the pygame runner_video.py while loop.

290

Source

 1#!/usr/bin/env python3
 2# /// script
 3# requires-python = ">=3.14"
 4# dependencies = ["simvx-core", "simvx-graphics", "numpy", "pillow"]
 5# ///
 6
 7# /// simvx
 8# tags = ["port", "tier-0"]
 9# upstream = "https://github.com/clear-code-projects/UltimatePygameIntro"
10# web = { width = 800, height = 400, responsive = true }
11# ///
12"""Pixel Runner: Clear Code's Pygame endless runner, with Timer signals, flipbook sprites and synthesised audio.
13
14Run::
15
16    simvx examples run ports_ultimate_pygame_intro
17    simvx examples run ports_ultimate_pygame_intro --test   # headless capture
18
19Web export::
20
21    simvx export web main.py -o pixel_runner.html
22"""
23
24import os
25import sys
26
27_HERE = os.path.abspath(os.path.dirname(__file__))
28
29from nodes.runner import Runner  # noqa: E402
30
31from simvx.graphics import App  # noqa: E402
32
33WIDTH, HEIGHT = 800, 400
34
35
36def main():
37    if "--test" in sys.argv:
38        from pathlib import Path
39
40        from simvx.graphics import save_png
41
42        capture_at = [30, 60, 120]
43        app = App(title="Pixel Runner", width=WIDTH, height=HEIGHT, visible=False)
44        frames = app.run_headless(Runner(), frames=130, capture_frames=capture_at)
45        out_dir = Path(_HERE) / "screenshots"
46        out_dir.mkdir(exist_ok=True)
47        for idx, img in zip(capture_at, frames, strict=False):
48            out_path = out_dir / f"frame_{idx}.png"
49            save_png(img, out_path)
50            print(f"saved {out_path}")
51    else:
52        App(title="Pixel Runner", width=WIDTH, height=HEIGHT).run(Runner())
53
54
55if __name__ == "__main__":
56    main()