No description
  • Odin 81.3%
  • GLSL 5.8%
  • HTML 5.4%
  • Nix 5.3%
  • Shell 2.2%
Find a file
2026-09-13 00:25:07 +01:00
assets first pass 2026-08-25 01:01:38 +01:00
shaders web export 2026-09-13 00:25:07 +01:00
src web export 2026-09-13 00:25:07 +01:00
web web export 2026-09-13 00:25:07 +01:00
.gitignore web export 2026-09-13 00:25:07 +01:00
build_web.sh web export 2026-09-13 00:25:07 +01:00
flake.lock first pass 2026-08-25 01:01:38 +01:00
flake.nix web export 2026-09-13 00:25:07 +01:00
ols.json refactor 2026-09-11 09:38:15 +01:00
README.md web export 2026-09-13 00:25:07 +01:00
src.o more refactors 2026-09-11 18:52:17 +01:00

The Bird that Flaps, Odin port

An Odin and raylib port of ../../godot/the-bird-that-flaps.

The port includes the original gameplay, title and game-over screens, animated bird, moving pipe variant, slowdown and speedup power-ups, sound effects, score persistence, virtual 456x256 rendering, and aspect-preserving window scaling.

Controls

  • Space or left mouse button: flap
  • Enter: start or retry
  • R: reset the game
  • Escape: quit

Requirements

  • Odin
  • raylib 6.0

The code uses Odin's official vendor:raylib bindings.

Run

On NixOS, enter the development shell first:

nix develop
odin run ./src

On other systems with Odin and raylib installed:

odin run ./src

Web build

The Nix development shell includes Emscripten and a web build of raylib:

nix develop
./build_web.sh

This creates:

  • build/web/: files for local hosting
  • build/the-bird-that-flaps-web.zip: the archive to upload to itch.io

Preview it locally with:

python3 -m http.server 8000 -d build/web

Then open http://localhost:8000. Browsers do not allow the build to run by opening index.html directly.

On itch.io, set Kind of project to HTML, upload build/the-bird-that-flaps-web.zip, and choose an embedded viewport of 912x512 or click-to-launch fullscreen. The canvas also adapts to other viewport sizes.

The desktop best score is stored in the platform user data directory under the-bird-that-flaps/score.save. The web build stores it in browser local storage.

Structure

  • src/main.odin: window/audio lifetime and the frame loop
  • src/shared/: Drawable/Screen base types with receiver-based update/draw procedure fields
  • src/app/: drawable root application; active screen, screen changes, and render pipeline
  • src/title/: title screen; owns its state as a package variable, Init returns a wired Title (a Screen subtype)
  • src/gameplay/: gameplay screen; world, HUD, death flash, audio, score persistence, and popup lifecycle
  • src/gameover/: drawable game-over popup with its own score snapshot, animation clock, retry button, and result events
  • src/transition/: drawable timed screen fade that reports the covered-frame screen switch
  • src/button/: drawable button state; clicked is reset and recomputed every update
  • src/input/: shared per-frame input snapshot
  • src/game/: simulation state, player physics, pipes/collisions, and timed power-ups; no raylib, UI, audio, or storage dependencies
  • src/rendering/: render-target lifetime/resizing, compositing, world drawing, and sprite helpers
  • src/assets/: texture, audio, and shader loading
  • src/storage/: best-score persistence
  • src/view/: virtual resolution, DPI scaling, mouse mapping, and viewport bounds
  • shaders/effect.fs: slowdown and speedup post-processing
  • assets/: runtime-ready art and audio extracted or copied from the Godot project

Screens and popups

Screens embed shared.Screen, which provides enter, exit, update, draw, and take_result procedures with no tagged-union dispatch. Each screen package keeps its state in a package variable. App initialization creates only the title screen. At the covered point of a transition, the app exits and releases the active screen, initializes the target screen, and enters it. Retry returns to a newly initialized title screen, matching the original flow.

The gameplay screen exclusively owns its drawable game-over popup and updates/draws it like any other child. The popup does not inspect the world or save scores. gameover.Open activates it and gameover.Take_Result reports a retry request and a one-time score-revealed event to gameplay. Gameplay persists new best scores and converts retry into a generic screen result, so neither shared screen state nor the app knows scores exist.

Gameplay updates return small, frame-local events. The screen layer handles their sound and UI effects, so simulation can run without a window or audio device.

Credits

The source project credits MegaCrash for Flappy Bird assets and the original Flappy Bird sound set. The copied audio license is in assets/audio/LICENSE.

Odin documentation: https://odin-lang.org/docs/

Odin raylib package documentation: https://pkg.odin-lang.org/vendor/raylib