Skip to content

GameObjects & Components ​

Everything in an Awaken scene is a GameObject built from Components - small bundles of typed data. This page explains the entity-component model underneath, and how one reflection system drives both the Inspector and saving.

The model: entities + components ​

Awaken uses a data-oriented Entity-Component-System (ECS). There are three moving parts:

  • an Entity is a lightweight handle - just a number,
  • a Component is a plain typed object of data attached to an entity,
  • Systems (physics, animation, scripts) run each frame over entities that have the components they care about.

A GameObject is the friendly, object-oriented face of an entity you interact with in the editor and in scripts. It carries no data of its own - it's a thin wrapper over (world, entity) that forwards to the World.

Entities are handles ​

An entity is a single integer that packs an id and a generation counter. The generation is what makes stale references safe: when an entity is destroyed its id is recycled, but the generation ticks up, so an old handle to that slot no longer matches and reads as not alive. Handle 0 is reserved as NULL_ENTITY (meaning "none" / "no parent"). You rarely see these numbers - the editor shows names - but it's why deleting an object never corrupts other references.

Components are typed data ​

Each component type has its own sparse-set store in the World, keyed by entity. Adding, reading, checking, and removing a component are all direct operations, and you can query for every entity that has a given set of components. A GameObject exposes the same operations:

ts
const go = createGameObject(world, "Crate");
go.addComponent(MeshRenderer, { mesh: "cube", color: [0.8, 0.6, 0.4] });
const mr = go.getComponent(MeshRenderer);   // read
go.hasComponent(RigidBody);                 // check
go.removeComponent(Collider);               // remove

createGameObject always ensures a Transform exists, so every object has a place in space and shows up in the hierarchy.

The registered component types ​

The editor registers a fixed set of engine component types (REGISTRY in apps/editor/src/scene.ts). Each appears in the Inspector's searchable + Add Component… list (sorted by name), gets an Inspector card, and is serialized. Components a plugin registers join the same list.

+ Add Component… in the Inspector lists every registered component type, alphabetically, with a search box.

ComponentPurpose
TransformPosition, rotation, scale. Every object has one. See Transforms.
MeshRendererDraws a mesh with a colour or texture and PBR values, plus shadow, sort-order, light-layer and render-layer settings.
MeshMaterialBinds a reusable material asset by id, with per-object parameter overrides.
LightA directional, point or spot light. See PBR & Lighting.
CameraA viewpoint: perspective (uses fov) or orthographic (uses orthoSize, a half-height in world units), near/far, a Fit and Turn for 2D views, and an optional Target texture that makes it draw into a render texture. See Cameras.
AnimatorPlays skeletal animation clips (crossfade, root motion). See Animation.
AnimStateMachineDrives the Animator from a parameter-driven state graph, with per-character clip bindings. See Animation.
RagdollHands a skinned character to physics so it goes limp (via api.ragdoll). Needs Play; does nothing in edit mode.
BoneAnchorMakes an entity follow a named skeleton bone so attached props (sword, cape) ride the animation.
SkinBonesKeeps an imported skinned mesh live: its bone child objects drive the mesh by CPU skinning, so rotating a bone (gizmo or api.setBoneRotation) bends it. Added by the importer.
RigidBodyPhysics body: velocity, gravity, restitution. See Rigidbody.
ColliderOne or more collision shapes, plus collision layer, mask, one-way and friction. See Colliders.
AudioSourcePlays a sound (volume, loop, play on start, MIDI overrides). See Audio.
ScriptOne or more script behaviours and their parameters. See Scripting.
TagA string label that api.findByTag matches exactly.
UINodeA screen-space UI element (text, panel, button or image) drawn as a DOM overlay over the canvas. See UI.
UIScreenGroups UI elements into one screen that shows and hides as a unit (api.ui.showScreen), with layout variants by window size and game-area insets. See UI.
UIAnimatorPlays a UIClip on this widget's UINodes, driven by api.ui.play / stop.
ParticleEmitterPlays a ParticleEffect asset from this object.
VolumeA local or global region that blends its own effect cards (grade, bloom, fog...) over the scene's while the camera is inside. See Post-processing.
LodGroupSwaps the object's mesh for cheaper ones by camera distance, with 10% hysteresis. Imported LOD groups and terrain chunks use it. See Rendering performance.
TerrainTies an entity to a heightfield terrain asset; api.terrain answers from it. Set by the tool that built the terrain. See Terrain.
SimulationRuns a Simulation asset (GPU steps into render textures) on the fixed clock.
TextureProbeReads a render texture around the object's position for its script (api.probe). See Simulations.
Shape2DA rounded box, circle or capsule in the XY plane that draws itself and can be a 2D collider shape. See 2D Games.
PrefabLinkMarks an object as part of a linked prefab instance. Managed by the editor and never offered in Add Component.

Built-in object kinds ​

The Create menu (the + button in the Hierarchy header, a right-click on empty Hierarchy space, or Create Child on a row's right-click menu) spawns a ready-made GameObject. Each kind is a preset bundle of the components above:

MenuComponents it adds
FolderTransform only, at the origin, placed beside the current selection (or inside the row for Create Child). Drag rows into it, or use Group into Folder (⌘G).
Cube / Sphere / Plane / Cylinder / CapsuleTransform + MeshRenderer using the matching built-in mesh.
EmptyTransform only: a pivot to parent other objects under.
Directional Light / Point LightTransform + Light of that kind.
CameraTransform + Camera (perspective).
2D ▸ SpriteTransform + MeshRenderer on the built-in quad mesh (a flat card in the XY plane, facing +Z), transparent, shadow casting off. Assign a texture in the Inspector.
2D ▸ 2D Camera (Orthographic)Transform + Camera with projection: "orthographic" and orthoSize 5 (10 world units tall), at z = 10 looking down -Z.
2D ▸ Shape 2D ▸ Box / Circle / CapsuleTransform + Shape2D + MeshRenderer + MeshMaterial bound to the project's Shape Fill material (created the first time).
2D ▸ Distance Field CameraAn orthographic camera that draws every Shape 2D it sees as signed distance into a render texture.
2D ▸ Render Texture Demo / Game of Life / Ripples / Particle FieldWorking examples built from render textures and Simulations. See 2D Games.
UI ▸ Text / Panel / Button / ImageTransform + UINode of that kind. See UI.

Plugins can add their own entries at the bottom of the menu (the terrain example plugin adds Terrain (flat), Terrain Stamp and Terrain World); those place their objects themselves.

A top-level object lands where the camera is looking (surface-snapped under the drop point); adding a child places it at the parent's local origin. Every add is one undoable step.

The enabled flag ​

Every component carries a per-component enabled toggle, in the style of Unity's Behaviour.enabled. It is driven by the checkbox in each component's Inspector header. When a component is disabled, systems skip it - a disabled Script stops updating, a disabled Collider stops colliding - without removing the component or losing its settings.

This is distinct from the GameObject's own active flag, which enables or disables the whole object:

  • Component enabled - turns one component off, leaving the rest of the object working.
  • GameObject active - turns the object off. An object is only truly visible/simulated if it and every ancestor are active (active-in-hierarchy), so disabling a parent disables the whole subtree.

Both are undoable edits (see Editing Operations).

The Inspector for a cube with an AudioSource whose header checkbox is unticked (disabled, its fields greyed) under the MeshRenderer.

Transient entities: generated, not authored ​

An entity can be marked transient, meaning it was generated from data that can rebuild it: a streamed terrain chunk, a pooled runtime object, anything a script built. serializeWorld skips transient entities, so saving a scene while a generator is running writes the generator, not its output.

That matters for more than file size. Regenerable output in a save comes back on load as authored objects, which the generator then duplicates.

It is the entity-level twin of the transient runtime-mesh namespace described under runtime geometry, and objects created by api.mesh.place are marked transient for you. The flag changes nothing about what renders or where, so it is not a structural change and does not bump structureRev.

Reflection: one schema, three jobs ​

Components aren't hand-wired into the UI or the save format. Each is declared once with defineComponent(name, fields, create), where fields is a list of FieldSpec entries describing each property's name, type, and default:

ts
export const Transform = defineComponent<Transform>(
  "Transform",
  [
    { name: "position", type: "vec3", default: { x: 0, y: 0, z: 0 } },
    { name: "rotation", type: "quat", default: { x: 0, y: 0, z: 0, w: 1 } },
    { name: "scale",    type: "vec3", default: { x: 1, y: 1, z: 1 } },
  ],
  () => ({ position: vec3(), rotation: quat(), scale: vec3(1, 1, 1) }),
);

That single declaration is read by three subsystems:

The available field types are number, bool, string, vec3, vec4, quat, color, enum, assetRef, entityRef, plus composite types (colliderShapes, scriptList, scriptParams, materialParams, entityRefMap, clipMap, renderEffects, effectOverrides) that render as richer Inspector blocks. Because the schema is the single source of truth, adding a field to a component makes it appear in the Inspector and be saved, with no separate wiring.

A FieldSpec can also carry hints that only the editor reads (packages/core/src/component.ts):

KeyEffect
showIfHides the field when it returns false, for example a Collider's radius for a mesh shape.
hideWithHides the field when the object also has the named component, for example Animator.clip once an AnimStateMachine drives it.
readOnlyShows the field disabled, for a value a system or tool owns (Terrain.asset).
min, max, step, sliderTurn a number into a range control.
mask: "renderLayers"Shows a number as the render-layer picker.
placementMarks a field that places the object (Transform position and rotation, a UINode's anchor, x and y); on a prefab instance's root it stays the instance's own.
helpThe text behind the Inspector's ? marker for that field. Components carry a component-level help too.

One key changes the file: omitDefault leaves a field out of a save when it equals its default, so adding a field to a component does not rewrite every saved scene.

defineComponent also auto-appends the enabled field to every component, so the enabled toggle serializes like any other property. In memory an untouched component leaves enabled unset (which reads as enabled) and only records false once you disable it; when a game is exported, fields equal to their default are dropped from the shipped scene to shrink the file, and the player backfills them.

Reference-typed fields (entityRef, entityRefMap, and a Script's refs) are special-cased at save time: entity handles are rewritten to stable save-ids and remapped back on load, so a script that points at another object keeps pointing at it across save/reopen and across prefab instancing.

See also ​

Awaken: browser-native WebGPU game engine.