Overview

XII FlyBird is an easy-to-play endless flyer where you rack up points by soaring through procedurally generated gateways. Built in C++ with TypeScript scripting, FlyBird balances lightweight runtime performance with flexible in-game tuning via console variables.

Motivation

Designing a small-scale game often leads straight to “just get it working.” FlyBird’s goal was different: I wanted an endless runner that felt polished, ran smoothly on modest hardware, and supported live tweaking of gameplay parameters without recompiling. By combining low-level C++ core systems and high-level TypeScript hooks, I aimed to:

  • Achieve a smooth 60 FPS on integrated GPUs.
  • Expose key gameplay parameters (speed, gap size, obstacle density) through CVars.
  • Build a simple finite-state animation framework for bird sprites.
  • Recycle obstacles efficiently to keep memory usage constant.

Core Features

  • Procedural obstacle generation that guarantees random yet fair gate placement.
  • Console vars (CVars) for real-time adjustment of player impulse, obstacle spacing, and camera shake.
  • State-based animation driven by a finite state machine for smooth transitions.
  • Low-level physics impulses for natural bird “flight” and gravity behavior.
  • High-level scripting in TypeScript for obstacle behaviors and game flow control.

Architecture

FlyBird is split into three layers:

  1. Engine Core (C++):
    • Memory-pooled object system for reuse of obstacle and particle instances
    • Physics impulses for forward motion and flapping
    • CVar console subsystem for live-tuning of floats, ints, and booleans
  2. Scripting Layer (TypeScript):
    • Defines obstacle spawn logic and random height ranges
    • Manages game states: Menu → Playing → GameOver
    • Binds CVar changes to in-game UI overlays
  3. Data & Assets:
    • Prefabs for bird, gates, and background elements
    • Scriptable configuration in RuntimeConfigs for default CVar values
    • Editor plugins for rapid scene iteration

Procedural Pipeline

The obstacle pipeline follows this flow:

Seed RNG → Compute Next Gate Position → Instantiate or Recycle Obstacle → Apply Random Z-Offset → Begin Movement Toward Player

Key parameters exposed as CVars include:

ParameterDefaultDescription
cvar_PlayerSpeed300.0Forward impulse magnitude.
cvar_PlayerFlapImpulse450.0Upward impulse when “fly” input is active.
cvar_ObstacleSpacing75.0Distance between consecutive gates.
cvar_ObstacleVerticalRange60.0Max vertical displacement for gate openings.

This approach ensures obstacles loop seamlessly and maintains constant memory overhead.

Gameplay Mechanics

Players control the bird with a simple “fly” key or button:

  • Hold “Fly” to apply upward impulse, counteracting gravity.
  • Release to let gravity descend the bird naturally.
  • Each passed gate awards one point; colliding ends the run.
  • Dynamic difficulty scaling: after every 10 points, cvar_ObstacleSpacing decreases by 5%.

Performance & Optimization

Through targeted optimizations, FlyBird consistently hits 60 FPS on entry-level hardware:

  • Object pooling avoids frequent allocations/deallocations.
  • Minimal per-frame TypeScript invocations; heavy logic runs natively.
  • Simple shaders for background parallax and gate highlights.
  • Batch-rendering of sprites to reduce draw calls.

Frame times measured over 10 runs:

HardwareAverage FPSPeak Memory Footprint
Intel UHD Graphics6045 MB
Integrated AMD GPU6050 MB
Mid-range dGPU (GTX)14475 MB

Technical Challenges

  • Precision Loss Over Time: Early pooling logic led to floating-point drift. Solved by resetting object positions relative to a fixed origin every 1 km traveled.
  • Live-Tuning Overhead: Unbounded CVar updates caused hitches. Introduced a “dirty” flag so changes apply at frame boundaries.
  • State-Sync Between C++/TS: Ensuring that script-driven obstacle logic and core physics remained in lock-step required a single “tick” dispatcher.

Lessons Learned

  • Embedding a lightweight scripting layer vastly improved iteration speed compared to pure C++ builds.
  • Procedural generation works best when parameters are exposed early; waiting to tune in code wastes playtesting time.
  • Consistent memory usage is critical for endless games; object pools keep the GC (or OS allocator) from becoming a bottleneck.

Future Work

  • Add shader-based post-processing (motion blur, depth fog) for visual polish.
  • Implement online leaderboards with networked score submission.
  • Mobile port with touch controls and adjustable CVar presets.
  • VR/AR adaptation for immersive first-person flying.