# Fixed-camera games and runtime presentation Forma supports fixed orthographic scenes that combine 3D geometry and transparent GLB sprite planes. This is optional: projects without these settings retain the existing camera, controls, and player UI. ## Camera and rendering An entity can have `camera: { mode: "fixed", lookAt: [0, 0, 0], projection: "orthographic", orthoWidth: 24, aspect: 16 / 9 }`. The camera uses the entity's world position, faces `lookAt`, and preserves the specified aspect ratio with letterboxing. Omit `aspect` to fill the current viewport. The inspector exposes fixed mode, projection, width and look-at position. Perspective and follow/first-person cameras remain supported. `settings.rendering` accepts `toneMapping`, `exposure`, `contrast` and `defaultLights`. Set `defaultLights: false` to use only authored lights, for example in an imported Blender scene. Disabling tone mapping is useful for artwork that already contains lighting. Generated mesh materials accept `unlit`, `alpha` (0–1) and `doubleSided`; scripts can update them through `api.patch`. Imported GLB materials retain their authoring settings unless a material override is requested. ## Controls `settings.controls` optionally maps `attack`, `jump`, `dash`, `sprint` and `reset` to arrays of browser `KeyboardEvent.code` values. Empty arrays disable a keyboard action. Mouse attack and pointer aiming retain the existing runtime input API. Movement remains WASD/arrows; a project's script may transform those axes into its camera basis. Dash/jump/reset also have one-frame pressed events. ## Presentation Optional `settings.presentation` enables the same lightweight overlay in the editor and exported standalone player: ```json { "title": "Example game", "accent": "#d74732", "instructions": "WASD — move · J — attack · Space — dash", "start": { "title": "Example game", "body": "Reach the exit." } } ``` The start screen pauses simulation until the player continues. The overlay supplies pause/resume, fullscreen, sound, touch movement/attack/dash and keyboard restart. On restart it emits `resetPressed`; the project script owns resetting its gameplay state. Stop in the editor restores the original scene document. Scripts can emit these presentation events: ```js api.emit("hud", { objective: "Wave 1 / 3", counters: ["Enemies: 4", "Defeated: 0"], meters: [ { id: "life", label: "Health", value: 4, max: 5, style: "hearts" }, { id: "dash", label: "Dash", value: 0.7, max: 1, style: "bar" } ] }); api.emit("hud", { overlay: { title: "Victory", body: "You reached the exit.", button: "Play again" } }); api.emit("hud", { overlay: null }); // Clear an outcome overlay when resetting. api.emit("feedback", {}); // Brief damage vignette. api.emit("sound", { frequency: 520, endFrequency: 90, duration: 0.1, type: "triangle", gain: 0.025 }); ``` Outcome overlays pause simulation. HUD text is assigned as text content, meter values and list lengths are bounded. Audio starts after a user gesture and can be muted. Runtime callbacks continue receiving these events, including during headless tests. The concrete arena, artwork, combat, enemy AI and wave progression live in `projects/InfernalCourtyard`, not in the engine or default project templates.