GameObjects & Components
Everything in a Awaken3D 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
Awaken3D 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); // removecreateGameObject 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 knows about a fixed set of component types - these appear in the Add-Component menu, get an Inspector, and are serialized:
| Component | Purpose |
|---|---|
| Transform | Position, rotation, scale - every object has one. See Transforms. |
| MeshRenderer | Draws a mesh with a color/texture and PBR values. |
| MeshMaterial | Binds a reusable material asset by id. |
| Light | Directional or point light. |
| Camera | A viewpoint: perspective (uses fov) or orthographic (uses orthoSize, a half-height in world units), with near/far clip planes. The first active, enabled camera renders. See Cameras. |
| Animator | Plays skeletal animation clips (crossfade, root motion). See Animation. |
| AnimStateMachine | Drives the Animator from a param-driven state graph, with per-character clip bindings. See Animation. |
| Ragdoll | Hands a skinned character to physics so it goes limp on death/impact (via api.ragdoll). Experimental. |
| BoneAnchor | Makes an entity mirror a named skeleton bone so attached props (sword, cape) ride the animation. |
| RigidBody | Physics body - mass, velocity. See Rigidbody. |
| Collider | One or more collision shapes. See Colliders. |
| AudioSource | Emits a sound. See Audio. |
| Script | Attaches one or more script behaviours + their parameters. See Scripting. |
| Tag | A named label for finding objects from scripts. |
| UINode | A screen-space UI element - text, panel, button, or image - drawn as a DOM overlay over the canvas. See UI. |
| UIAnimator | Plays a UIClip on this widget's UINodes (fade / slide / pulse / spin), driven by api.ui.play/stop. See UI. |
| ParticleEmitter | Plays a ParticleEffect asset from this object; the asset owns the look, the component picks which one. See Particles. |
Built-in object kinds
The Add menu (the + button in the Hierarchy, or a right-click on an object to add a child) spawns a ready-made GameObject - each kind is just a preset bundle of the components above:
| Menu | Kind | Components it adds |
|---|---|---|
| Cube / Sphere / Plane / Cylinder / Capsule | primitive | Transform + MeshRenderer using the matching built-in mesh. |
| Empty | grouping node | Transform only - a pivot to parent other objects under. |
| Directional Light / Point Light | light | Transform + Light of that kind. |
| Camera | camera | Transform + Camera (perspective). |
| 2D → Sprite | 2D sprite | Transform + MeshRenderer on the built-in quad mesh (a flat card in the XY plane, facing +Z), created transparent with shadow casting off. Assign a texture in the Inspector. |
| 2D → 2D Camera (Orthographic) | 2D camera | Transform + Camera with projection: "orthographic" and orthoSize 5 (10 world units tall), placed at z = 10 looking down -Z. Pair it with Sprites for a 2D or isometric view. |
| UI → Text / Panel / Button / Image | UI element | Transform + UINode of that kind. See UI. |
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).
📸 Screenshot - save as img/world-component-enabled.png
The Inspector for a selected object showing several components stacked, with one component's header checkbox unticked (disabled) and the GameObject active checkbox at the top.
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, quat, color, enum, assetRef, entityRef, plus composite types (colliderShapes, scriptList, scriptParams, materialParams, entityRefMap, clipMap) 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 - no separate wiring. A field can also declare showIf to hide itself when it's irrelevant (for example, a Collider's radius is hidden for a mesh shape).
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
- Component reference - the full field list for each component type.
- Transforms & Hierarchy - the component every object has.
- Inspector - editing component fields.
- Core Concepts - the engine's overall mental model.
- Editing Operations - add/remove/enable components as undoable steps.