Clumsy Bird¶
Flappy-style endless flyer with audio, score and restart.
▶ Run in browserUpstream: https://github.com/ellisonleao/clumsy-bird
Licence: this port's own code is offered under GPL-3.0-or-later, 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
Clumsy Bird (SimVX port)¶
SimVX port of ellisonleao/clumsy-bird, a MelonJS Flappy Bird clone: flap through pipe gaps, score per pipe passed, crash restarts.
Licensing: GPL-3.0-or-later (matching upstream code); all art is CC0 (Kenney
Tappy Plane) or procedural, all audio synthesised. See ATTRIBUTION.md.
Run¶
simvx examples run ports_clumsy_bird # interactive
simvx examples run ports_clumsy_bird --test # headless capture
simvx export web main.py -o clumsy_bird.html
Controls¶
Tap / Space / controller A: flap (also starts a run, and restarts after a crash). The whole screen is the button, so a phone needs no on-screen pad.
Esc: quit
What it shows¶
One action, three devices: the root scene declares
input_context = {"flap": [Key.SPACE, Mouse.LEFT, Controller.A], "quit": [Key.ESCAPE]}, so keyboard, mouse, touch and a controller share one code path. The web runtime delivers a tap as a left click, which is what makes the mouse binding the touch route. The context is in force while that scene is mounted; each scene declares its own, so a swap brings the new one’s in.Event-driven input: the flap is handled by
@on_input("flap"), which runs once per press, before any physics tick, and is handed the event that caused it.A layout with no baked window size: the ground line, the bird’s rest height, the pipe gaps and the background are all computed from
self.viewport_sizein the hook that needs them, so the game plays at any window size and follows a resize.config.pynames the size the window opens at and nothing else.Readouts ignore the pointer: the score, the hint strip and the game-over panel set
pointer_filter = PointerFilter.IGNORE, so a tap that lands on text still reaches the game.Coroutines: pipe spawning is a generator driven by
start_coroutineandwait, not a hand-rolled timer field.Signals: the bird emits
flappedandcrashed, each pipe pair emitspassed; the play scene connects them and keeps the score.AnimatedSprite2D: a three-frame flipbook drives the wing beat.
Procedural audio: the wing, score, crash and looping theme are synthesised at load with
AudioSynth, so the port ships no sound files.Anchored UI: the score readout, title card, bottom controls strip and game-over panel are
Controls positioned with anchors and margins.Scene flow: the title screen and every restart are
tree.change_sceneswaps.
Source files¶
File |
Summary |
Lines |
|---|---|---|
Clumsy Bird: Flappy-style endless flyer with audio, score and restart. |
59 |
|
Shared constants and asset paths for Clumsy Bird. |
72 |
|
Clumsy Bird scene nodes. |
1 |
|
Procedural audio: built on the engine’s AudioSynth API. |
129 |
|
Player bird: gravity, flap, rotation. |
158 |
|
Scrolling ground: two side-by-side ground sprites that loop, plus a |
65 |
|
Score HUD: anchored Label that displays the current score. |
27 |
|
MenuScene: the title screen shown before a run starts. |
116 |
|
Pipe pair: top + bottom pipe sprites that scroll right to left and emit |
88 |
|
PlayScene: the active game: bird, pipes, ground, HUD, score, restart. |
286 |
Source¶
1#!/usr/bin/env python3
2# /// simvx
3# tags = ["port", "tier-0"]
4# upstream = "https://github.com/ellisonleao/clumsy-bird"
5# web = { width = 900, height = 600, responsive = true }
6# ///
7"""Clumsy Bird: Flappy-style endless flyer with audio, score and restart.
8
9Run desktop: simvx examples run ports_clumsy_bird
10Headless: simvx examples run ports_clumsy_bird --test
11Web export: simvx export web main.py -o clumsy_bird.html
12
13Controls:
14 Tap / Space / (A) Flap (also starts a run and restarts after a crash)
15 Esc Quit
16"""
17
18# /// script
19# requires-python = ">=3.14"
20# dependencies = ["simvx-core", "simvx-graphics", "numpy", "pillow"]
21# ///
22
23from __future__ import annotations
24
25import sys
26from pathlib import Path
27
28_PORT_DIR = Path(__file__).resolve().parent
29
30from config import WINDOW_HEIGHT, WINDOW_WIDTH # noqa: E402
31from nodes.menu import MenuScene # noqa: E402
32from nodes.play_scene import PlayScene # noqa: E402
33
34from simvx.graphics import App # noqa: E402
35
36
37def main():
38 test_mode = "--test" in sys.argv
39 app = App(width=WINDOW_WIDTH, height=WINDOW_HEIGHT, title="Clumsy Bird", visible=not test_mode)
40
41 if test_mode:
42 from simvx.graphics import save_png
43
44 out = _PORT_DIR / "screenshots"
45 out.mkdir(exist_ok=True)
46 # A headless run gets no input, so boot straight into the play scene:
47 # the captures then show the game rather than the title card.
48 frames = app.run_headless(PlayScene(), frames=180, capture_frames=[60, 120, 179])
49 for idx, n in enumerate([60, 120, 179]):
50 save_png(frames[idx], out / f"frame_{n}.png")
51 print(f"Wrote {len(frames)} screenshots to {out}")
52 app.quit()
53 return
54
55 app.run(MenuScene())
56
57
58if __name__ == "__main__":
59 main()