Game Loop
How the main loop is driven and how per-frame logic is organized.
Loop Architecture
Galaxy Eggbert does not implement its own game loop. The engine (Urho3D/U3D) owns the loop
and fires an E_UPDATE event every frame. The application subscribes to this event
and feeds the delta time to the game.
Event Flow
// Engine internal loop (Urho3D):
while (!shouldExit) {
engine->RunFrame(); // fires E_UPDATE, renders, etc.
}
// GalaxyEggbertApp subscribes at Start():
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(GalaxyEggbertApp, HandleUpdate));
// Every frame:
void GalaxyEggbertApp::HandleUpdate(StringHash, VariantMap& eventData) {
using namespace Update;
float dt = eventData[P_TIMESTEP].GetFloat();
if (game_) game_->Update(dt);
}
Per-Frame Update Sequence (GalaxyEggbertGame::Update)
GalaxyEggbertGame::Update(float dt)
│
├─ Check phase-agnostic keys:
│ ESC in Init → engine->Exit()
│ F1 → toggle debug overlay
│
├─ Switch on phases_->Current():
│
│ ┌─ GamePhase::Play → UpdatePlay(dt)
│ │ ├─ ESC → Pause phase
│ │ ├─ blupi_->Update(dt)
│ │ │ ├─ Read input (UP/DOWN/LEFT/RIGHT/W/A/S/D/Q/E/Space)
│ │ │ ├─ Apply gravity: vel_.y_ += kGravity * dt
│ │ │ ├─ Integrate position
│ │ │ ├─ ResolveY() ← vertical AABB collision
│ │ │ ├─ ResolveXZ() ← horizontal AABB collision
│ │ │ ├─ Fall death check (pos.y_ < -10)
│ │ │ ├─ Update animation state
│ │ │ └─ Invincibility flash update
│ │ ├─ Sound triggers (jump, land)
│ │ ├─ Fall death handling → life deduct or Lost phase
│ │ ├─ camera_->Update(dt, blupiPos, blupiYaw)
│ │ ├─ Countdown timers (shield, invincible, hint)
│ │ ├─ Tile hazard check (Lava/Spike/Crusher under Blupi)
│ │ ├─ decor_->Update(dt, blupiPos)
│ │ │ ├─ StepMovement() for each active object
│ │ │ ├─ Platform carry delta accumulation
│ │ │ ├─ UpdateIcon() → ObjectNode UV update
│ │ │ └─ TouchesBlupi() → event flags
│ │ ├─ Process decor events:
│ │ │ shield pickup, key pickup, treasure pickup,
│ │ │ bonus life, platform carry, exit reached,
│ │ │ enemy hit
│ │ └─ hud_->ShowPlay(lives, collected, total, keys, shield, world)
│ │
│ ├─ GamePhase::Init → UpdateInit(dt)
│ │ ├─ Rebuild gamer-select text
│ │ ├─ Keys 1/2/3 → SelectGamer()
│ │ └─ S → MainSetup
│ │
│ ├─ GamePhase::Pause → UpdatePause(dt)
│ │ ├─ ESC → Play
│ │ └─ S → PlaySetup
│ │
│ ├─ GamePhase::Win → UpdateWin(dt)
│ │ └─ Any key/click → AdvanceToNextWorld()
│ │
│ ├─ GamePhase::Lost → UpdateLost(dt)
│ │ └─ Any key/click → ResetLevel()
│ │
│ └─ GamePhase::MainSetup / PlaySetup → UpdateSettings(dt)
│ ├─ S → toggle sound
│ └─ ESC → return to caller
│
└─ If debug mode: oct->DrawDebugGeometry()
Timestep
The timestep (dt) is the wall-clock time since the last frame in seconds,
provided by the Urho3D engine. Galaxy Eggbert uses variable-timestep physics — all motion
and timers multiply by dt directly.
Web Build Timing (Nova3D / Emscripten)
The Web build uses a fixed-timestep accumulator in Nova3D/CNA's Game.cpp.
The browser calls the RAF callback at ~60 Hz; real inter-frame time is measured and accumulated,
and Update() fires only when one full TargetElapsedTime slice has been accumulated.
A 250 ms spike cap prevents runaway catch-up after a tab is backgrounded.
This ensures gameplay speed matches native desktop builds regardless of the browser's RAF cadence.
Rendering
Galaxy Eggbert does not call any explicit render functions.
Urho3D renders the scene automatically after the update events fire.
All visual changes (node positions, material updates, UI changes) are applied in the update phase
and the engine renders them at the end of RunFrame().