Level Scene Frame Hooks
This page is the ready-state hook reference for Hs2dLevelScene. See Level Scene Lifecycle for loading, one-time world construction, onWorldReady(), and restart behavior.
Ready Frame Template
Once a world exists, every update runs:
The default onRender() calls world.render(screen). Override it only to add phase telemetry or tightly bounded render behavior around the world render.
Input Phase
handleInputs(inputs) runs before game rules. Return true to consume the frame's update. This is useful for pause, quit, restart, and modal navigation.
protected override handleInputs(inputs: GameInput[]): boolean {
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
if (input.release) continue;
if (input.isButton('play')) {
if (this.game) {
this.game.pushScene(new MyPauseScene({
screenWidth: this.screenWidth,
screenHeight: this.screenHeight,
audioManager: this.audioManager,
onRestart: () => this.restartLevel(),
}));
}
return true;
}
if (input.isButton('back')) {
this.context.navigateToMenu();
return true;
}
}
return false;
}
Use onUpdate() for continuous movement and simulation. Use handleInputs() for discrete control flow and input state changes.
Game Rules Phase
onUpdate(deltaMs, inputs) is where simulation runs: movement, AI, timers, collision checks, score, health, spawns, and camera control.
protected override onUpdate(deltaMs: number, inputs: GameInput[]): void {
this.updatePlayerInput(inputs);
this.updatePlayer(deltaMs);
this.updateEnemies(deltaMs);
this.updateProjectiles(deltaMs);
this.checkProjectileHits();
this.tickSceneCamera(deltaMs);
}
Keep logical state in plain fields, fixed arrays, and pools. Do not move sprites here unless a helper is explicitly synchronizing render state. Save that projection work for updateSceneSprites().
One Camera Rule
A level scene has one active gameplay camera. activeCamera resolves to world.camera once the world exists, or bootstrapCamera during loading/build setup.
Tick the camera once per frame:
protected override onUpdate(deltaMs: number, inputs: GameInput[]): void {
void inputs;
this.cameraTarget.x = this.playerX;
this.cameraTarget.y = this.playerY;
this.tickSceneCamera(deltaMs, this.zoom);
}
tickSceneCamera() calls world.tick({ deltaMs, scale, updateSpriteLayers: false }) when the world exists. Layers read the camera during world update/render. Do not create a separate camera per layer, projectile system, or HUD.
Sprite Sync Phase
updateSceneSprites() runs after game rules. It should copy logical state into renderer objects: positions, frames, visibility, z-index, collision data, and culling state.
protected override updateSceneSprites(): void {
const playerPool = this.playerSpritePool;
if (!playerPool) return;
const playerSprite = playerPool.getSprite(0);
playerSprite.setWorldPosition(this.playerX, this.playerY);
playerSprite.setFrame(this.playerFrameId);
playerSprite.setVisible(this.playerHealth > 0);
for (let i = 0; i < this.projectiles.items.length; i++) {
const projectile = this.projectiles.items[i];
const sprite = this.projectileSpritePool.getSprite(i);
if (!projectile.active) {
sprite.setVisible(false);
continue;
}
sprite.setWorldPosition(projectile.x, projectile.y);
sprite.setVisible(true);
}
}
This separation keeps gameplay testable and render objects pooled and stable.
Particles And Text
updateParticles(deltaMs) advances the configured Hs2dParticleLayer with particleEmitters and the active camera. Override it only when emitter positions need updating or optional counters need sampling.
protected override updateParticles(deltaMs: number): void {
if (!this.particles) return;
this.particleEmitters[0].x = this.playerX;
this.particleEmitters[0].y = this.playerY + 48;
super.updateParticles(deltaMs);
this.activeParticles = this.particles.getStats().activeParticles;
}
For HUD text, use drawCachedText() inside drawHud(screen) after the world is ready.
override drawHud(screen: GameScreen): void {
this.drawCachedText(
screen,
'score',
'SCORE ' + String(this.score),
36,
28,
0xfef08aff,
this.hudFont
);
}
Direct DrawText is acceptable for loading screens and short diagnostics. Gameplay HUD labels should use cached text.
Stats Window
When __TELEMETRY__ is enabled, updateStats(deltaMs) computes fps once per second, calls onStatsWindow(), resets frame counters, and resets telemetry accumulators. The call is compiled out when telemetry is disabled, so gameplay must not depend on onStatsWindow() for correctness.
protected override onStatsWindow(): void {
this.hudStatusText =
'FPS ' + String(this.fps) +
' E ' + String(this.visibleEnemies) +
' P ' + String(this.activeProjectiles);
this.collisionsThisSecond = 0;
if (this.particles) {
this.particles.resetCounters();
}
}
Use this hook only for optional performance HUD strings, counters, and logs. Put required save, expiry, spawn, or game-state work in normal update logic.
Render Phase
The default render phase is simple:
protected override onRender(screen: GameScreen, world: Hs2dWorld): void {
world.render(screen);
}
If you need telemetry around render, keep the override narrow:
protected override onRender(screen: GameScreen, world: Hs2dWorld): void {
this.markPhaseTimer();
world.render(screen);
if (__TELEMETRY__) {
this.telemetry.accumulatePhase('render');
this.frameCount++;
}
}
Do not run game rules from render. Do not create sprites, parse levels, or load images from render. Render composes existing world, layer, sprite, particle, and HUD state.
Do And Don't
Do:
- handle pause and quit before simulation
- run simulation in
onUpdate() - tick one camera once per frame
- synchronize renderer state in
updateSceneSprites() - update emitter positions before the base particle update
- keep render side-effect free except for telemetry-only timing and counters
Don't:
- parse Tiled objects in
onUpdate() - mutate gameplay state from
onRender()ordrawHud() - let each layer or system own a separate gameplay camera
- allocate arrays, closures, sprites, or text objects in steady-state hooks
- put required state changes in telemetry-only
onStatsWindow()
Source References
| Source | Frame-hook facts to verify |
|---|---|
../games/hosanna-ui/src/hosanna-game/hosanna2d/game/Hs2dLevelScene.ts | Ready update order, active camera helpers, sprite/particle hooks, render template, HUD helpers, telemetry gates, and stats window. |
../games/hosanna-ui/src/hosanna-game/hosanna2d/game/Hs2dScene.ts | Base scene hooks inherited by level scenes. |
../hosanna-ui-game-samples-public/src/hosanna-game-examples/native-shoot-em-up/VerticalShooterLevelScene.ts | Input, simulation, camera, sprite sync, particles, HUD, telemetry, and render implementations. |