From Idea to Game

From Idea to Game

Every engine demo starts at the finish line. This one starts at line one.

📄
blank file
World
Components
Entities
Systems
GPU
Lights
Materials
Post
Ship
Rich Game Scene
world • entities • rendering • post

A World From Nothing

Four lines. Your game exists.

const world = createWorld();
await world.initialize();
world.start();
World
loop:fixed-timestep (60 Hz)
render:demand-based
visibility:tab-aware pause
lifecycle:entity create / destroy
resources:lazy singletons
Frame Cycle
update
render
cleanup
60 Hz

What you got for free vs. what you didn't ask for — the engine starts empty.

Defining Your Game's Language

Your components are your vocabulary.

const Position = defineComponent({
  name: 'Position',
  schema: { x: f32, y: f32, z: f32 },
});
Game Design
"a thing with position"
"health that depletes"
"damage with element types"
Component Definitions
Position
{ x, y, z }
Health
{ current, max }
Damage
{ amount, element }

The design doc and the code are the same thing.

Components as Memory Layout

Your game design just became your memory layout.

defineComponent(Position)
x:f32
y:f32
z:f32
Type Definition
automatic
ArrayBuffer
x₀ y₀ z₀ x₁ y₁ z₁ x₂ y₂ z₂ ...
CPU Memory — contiguous, tightly packed
zero-copy
GPU Buffer
x₀ y₀ z₀ x₁ y₁ z₁ x₂ y₂ z₂ ...
GPU Memory — identical layout, no transform needed

Tags are zero-size markers — questions you ask about entities. No storage, just archetype membership.

Entities — Bringing Things to Life

An entity is a number. What makes it real is what you attach to it.

const player = world.entities.create();
world.batch((ctx) => {
  ctx.addComponent(player, Position, { x: 0, y: 0, z: 0 });
  ctx.addComponent(player, Health, { current: 100, max: 100 });
});
world.tags.add(player, Player);
Entity #1
just a number
Archetype Table
Position
x:0
y:0
z:0
Health
current:100
max:100
Player
tag
one archetype migration via batch

Spawning at Scale

Batch creation — 1,000 entities, one allocation.

const enemies = world.entities.createBatch(1000);
#2
#3
#4
...
#1000
#1001
one allocation
Contiguous Memory
Position[]
1000 entries, aligned
Health[]
1000 entries, aligned
Hierarchy
Player
Weapon
Particle
Particle
Particle

Systems — Making Things Happen

A system is a function that runs every frame. A query is what it cares about.

const MovementSystem = defineSystem({
  name: 'movement',
  queries: { movers: [Position, Velocity] },
  fn: (ctx) => {
    ctx.queries.movers.forEach((entity) => {
      ctx.write(entity, Position, 'x',
        ctx.read(entity, Position)!.x + vel.vx * ctx.dt);
    });
  },
});
Query
Archetype-matched iteration
No allocation per frame
ctx.write()
~5-10ns per field
Dirty tracking automatic
ctx.dt
Fixed timestep delta
Deterministic

ctx.write() — ~5-10ns per field. No allocation. Dirty tracking notes exactly which bytes changed.

Systems Compose

Dependency graph — systems declare ordering, the engine schedules.

60 Hz Fixed Timestep
input phase
InputPoller
query:[RawInput, DeviceMap]
InputActions
after: InputPoller
query:[RawInput, ActionMap]
PlayerControl
after: InputActions
query:[Player, ActionState]
update phase
Movement
after: PlayerControl
query:[Position, Velocity]
Combat
after: Movement
query:[Position, Damage, Health]

Register everything in the world with a single config object — components, systems, update rate.

Seeing Your World

One import. Your simulation becomes visible.

import { createGPU } from '@omni/gpu';
const gpu = await createGPU({ canvas });
const world = createWorld({ systems: [...], gpu });
ECS / CPU
Components
typed data columns
Systems
query & mutate
Entities
archetyped rows
TypeDescriptors
same schema drives both sides
GPU Pipeline
V-Buffer
visibility rasterize
Materials
PBR evaluation
Lighting
deferred shading
loadGltf(url) → entities with Mesh, Material, Transform

One line loads a 3D scene. Camera is just another entity with a component.

Player Control

Input is data. Actions are queries. Movement is a system.

const bindings = {
  [Action.MoveUp]: [{ keyboard: Key.W }],
  [Action.Jump]: [{ keyboard: Key.Space }],
  [Action.Attack]: [{ mouse: 0 }],
};
Keyboard
Mouse
Gamepad
Touch
InputActions
abstraction layer
ActionState
component on entity
PlayerControl
system

Same code works for every device. No conditionals.

Lighting Your World

A light is a component. Shadows are a flag. The GPU handles the rest.

STAGE 1
Scene
Flat shading
No lights
STAGE 2
Directional Light (sun)
Scene
Basic lit
STAGE 3
Point Lights
Scene
Multi-color
STAGE 4
Area Lights
Scene
Soft emitters
Clustered 3D Light Binning
2
0
5
3
1
0
1
8
6
4
2
0
0
7
3
5
2
1
each cell → light index list
Hundreds to thousands of lights. Per-pixel cost nearly constant.

CastShadow flag activates virtual shadow maps. ReSTIR for massive light counts.

Materials — Defining How Surfaces Look

PBR out of the box. Advanced materials when you need them.

Stone
roughness 0.85
Car Paint
clearCoat on
Silk
sheen on
Brushed
Steel
anisotropy 0.7
Glass
transmission 0.95
Skin
subsurface on
Not separate shaders. One material model. Pay only for layers you use.

Environment — Sky and Ambient Light

Your world needs a sky. The sky needs physics.

Atmosphere
Rayleigh:scattering
Mie:scattering
Ozone:absorption
Sun:direction vector
Image-Based Lighting
Irradiance Cubemap
diffuse
Prefiltered Cubemap
specular
BRDF LUT
split-sum lookup
Reflection Probes
Probe A
Probe B
Probe C
local IBL overrides for interiors
Planar Reflections
mirrors / water surfaces

Physically-based atmosphere from dawn to dusk. Environment changes → lookup tables recompute automatically.

Volumetrics — Fog, Clouds, and Light Shafts

The space between things matters.

const scene = createScene(gpu, {
  volumetricFog: true,
});
Volumetric Fog — Frustum Cross-Section
Camera
near → far
Froxel Grid (3D voxels)
·
·
·
·
·
·
·
·
·
·
·
·
• light source → in-scattering · empty voxel
Volumetric Clouds
Ray March
Shape + Detail Noise
Beer-Lambert + Multi-Scatter
Temporal Reproject
Clouds cast shadows. Wind pushes them. Sunset tints them gold.

Post-Processing — The Cinematic Pass

Raw rendering is a photograph. Post-processing is cinematography.

Essential Stack
Auto-Exposure
Bloom
Tone Mapping
TAA
FXAA
Quality Stack
GTAO
SSR
Motion Blur
Depth of Field
Film Effects
Final Output
optional upscaling
Temporal Upscaling
Render at 50–75%, reconstruct to native. AAA quality on integrated GPUs.

Each effect is independent — add or remove without touching the rest.

Animation — Movement Without the CPU

The GPU animates. The CPU dispatches.

const scene = createScene(gpu, {
  animation: { maxSkeletons: 500 },
});
CPU
Dispatch
one call per frame
GPU
Animation Clips
Bone Hierarchy
Vertex Skinning
Morph Targets

Hundreds of animated characters. Load from glTF — it just works.

Decals and Sprites

Paint the world after the fact. Give 2D sprites 3D superpowers.

Decals
Surface Geometry
albedo, normals, roughness
Decal OBB
projected overlay
Modifies Surface Properties
albedoblend decal texture
normalsreorient under decal
roughnessper-texel override
Coarse Tiles
Fine Pixels
Sprites
V-Buffer Pipeline (same as 3D)
2D Sprite
rendered as V-Buffer geometry
Inherited Features
lightingclustered lighting
shadowsvirtual shadows
AOambient occlusion
bloomHDR bloom
DoFdepth of field
motionmotion blur
2D and 3D share a pipeline — not a special mode.

Events — Decoupled Communication

Decouple cause from effect.

const DamageDealt = defineEvent({
  name: 'combat:damage-dealt',
  schema: { target: u32, amount: f32, element: u8 },
  pool: 64,
});
Combat System
emit(DamageDealt)
Event Bus
Particle System
spawnHitParticles
UI System
showDamageNumber
Audio System
playHitSound

Add a subscriber, combat never changes. Remove one, nothing breaks. Zero-allocation pooled events.

Scenes and Resources

Games aren't one scene. They're many. Not everything is an entity.

Scene Management
MainMenu
switchScene
PauseOverlay
pushOverlay
GameLevel
preload + switch
GameLevel 2
Overlay
Scene
Overlays push onto a stack above the active scene
Resources
World-Level Singletons
SpatialIndex
(BVH)
GameConfig
RNG
Created lazily. No globals. Clean dependency injection.

The Three Tiers of Performance

Start comfortable. Optimize when it matters. Go raw when you must.

Tier 1 — Prototyping
ctx.set(entity, Position, { x: newX, y: newY, z: newZ })
object-style, familiar
Tier 2 — Shipping
ctx.write(entity, Position, 'x', newX)
single-field, ~5-10ns
Tier 3 — Maximum Throughput
pos[base] += vel[base] * dt
raw TypedArrays, SIMD-ready
All three: zero allocation, same dirty tracking, freely mixable.

Debug Visualization

When something looks wrong, make the invisible visible.

28+ Debug Modes
Normals
Roughness
Metallic
Depth
Velocity
Light Count
heatmap
Shadow Cascades
Overdraw
heatmap
Instance IDs
Material Types

Toggle at runtime. Zero cost when disabled. No recompile.

The Full Picture

Every system you added — they all compose.

createWorld
Components
Position
Velocity
Health
Damage
Tags
Player
Enemy
Alive
Systems
Input
Control
Movement
Combat
AI
Animation
Rendering
GPU
V-Buffer
Materials
Lighting
Shadows
Environment
Volumetrics
Post
Sprites
Scenes
MainMenu
GameLevel
PauseOverlay
Events
DamageDealt
EnemyKilled
Resources
SpatialIndex
GameConfig
RNG

The Progression

From blank file to AAA-capable game. Each row is additive. No row requires the rows below it.

Step
What You Added
What You Got
World
3 lines
Game loop, lifecycle, scheduling
Components
Schema definitions
Memory layout, GPU compat, dirty tracking
Entities
Create + batch
Lifecycle, hierarchy, pooling
Systems
Functions + queries
Game logic, ordering, phases
GPU
One import
V-Buffer pipeline, GPU-driven rendering
Lights
Components + flags
Clustered lighting, shadows, area lights
Materials
Create + configure
PBR + 6 advanced material models
Environment
Scene config
Atmosphere, IBL, probes
Volumetrics
Flags
Fog, clouds, light shafts
Post
Pipeline config
18 cinematic effects
Animation
loadGltf
GPU skeletal + morph targets

Skip what you don't need. Add what you do. Start building.

Comments