Skip to content

Authoring workflow

Design levels with normal Godot nodes, convert them to ECS data at bake time, and spawn that data at runtime. The scene tree stays the level-design surface; the ECS World is the runtime simulation.

Concepts

  • Authoring node (EcsAuthoring3D / EcsAuthoring2D): a regular Node3D/Node2D you place in the editor with exported fields (mesh, health, velocity, …). It never simulates — it describes one entity.
  • Bake: converting an authoring subtree into an EntityScene resource (a serialized snapshot of entities + bake metadata). Baking is a cold path: allocations are allowed, and it runs in the editor or at scene setup, never in a hot loop.
  • EntityScene: a Godot Resource holding the baked bytes plus EntityCount, ComponentNames, and source keys for remapping internal entity references. Instantiate appends to a world without clearing it.
  • Spawning: creating live entities from an EntityScene at runtime, optionally scattered or distance-gated via EntitySceneInstance.

EcsAuthoring3D fields

GodotECS.Authoring.EcsAuthoring3D, extends Node3D. Every field is an exported property editable in the inspector:

FieldTypeDefaultBaked into
MeshMeshnullmesh group registration at sync time (RenderMeshRef, group resolved by MeshGroupManager.Register, stays 0 at bake)
MaterialOverrideMaterialnullmaterial used when the mesh group registers
IsStaticboolfalsetruePhysicsStaticTag + PhysicsRadius; falsePhysicsVelocity + PhysicsRadius
Radiusfloat0.5PhysicsRadius (clamped to 0.5 if ≤ 0.01)
Healthint100Health.Value
MaxHealthint100Health.Max (raised to Health if lower)
VelocityVector3zeroVelocity.Value and PhysicsVelocity.Value (dynamic only)
TintColorwhiteRenderColor

Every baked entity also gets LocalTransform (from GlobalTransform: position, rotation quaternion, uniform scale averaged from the basis), PhysicsLayer.Default, and a Parent component when the authoring node is nested under another baked entity.

Useful members: Entity Bake(World world) bakes just this node into a live world; EntityScene BakeScene() bakes it into a throwaway world and returns a standalone scene, cached on the Baked property.

EcsAuthoring2D fields

GodotECS.Authoring.EcsAuthoring2D, extends Node2D:

FieldTypeDefaultBaked into
TextureTexture2Dnullatlas group registration at sync time (SpriteRef, group stays 0 at bake)
SizeVector2(16, 16)SpriteSize
ModulateColorwhiteRenderColor
Healthint100Health (Value and Max)
Velocity2DVector2zeroVelocity.Value as Vector3(x, y, 0)

LocalTransform comes from GlobalPosition/GlobalRotation (Z-axis quaternion) and averaged GlobalScale. Same Bake / BakeScene / Baked members as the 3D variant.

Baker flow

GodotECS.Authoring.Baker is a static class with two entry points:

csharp
EntityScene scene = Baker.BakeSubtree(rootNode);   // bake to a new EntityScene
int count = Baker.BakeSubtreeInto(world, rootNode); // bake into an existing world, returns entity count

Behaviour:

  1. Pre-order traversal — each parent is baked before its children so the child's Parent component can reference the already-created parent entity.
  2. Only EcsAuthoring3D / EcsAuthoring2D nodes produce entities. Plain Node3D/Node containers produce nothing but their children are still visited, with the nearest baked ancestor as parent.
  3. BakeSubtree works on a throwaway World and returns EntityScene.FromWorld(tmp); BakeSubtreeInto writes directly into your world (e.g. World.Preview for editor ghosts) and returns the baked count.
  4. Both throw ArgumentNullException on a null root (or null world). No reflection is used; dispatch is by is checks.

Typical editor usage: select the level root, press the Bake ECS button (see Editor tools), save the resulting EntityScene as a .tres prefab.

Spawning with EntitySceneInstance

GodotECS.Authoring.EntitySceneInstance, extends Node. Exports:

FieldTypeDefaultMeaning
SceneEntityScenenullbaked scene to spawn
Countint1how many copies SpawnAll creates (negative → 0)
SpawnRadiusfloat5.0ring radius used to scatter copies when Count > 1
StreamDistancefloat100.0if origin.Length() > StreamDistance (and > 0), nothing spawns — cheap distance streaming
csharp
// One entity at a position in the default world.
Entity root = instance.SpawnDefault(new Vector3(10, 0, 5));

// One entity in an explicit world.
Entity root2 = instance.Spawn(World.Default, new Vector3(10, 0, 5));

// Count copies scattered on a ring around origin.
Entity[] roots = instance.SpawnAll(World.Default, Vector3.Zero);

Spawn returns Entity.Null when the scene is null/empty. SpawnAll returns an array of Entity.Null in the same cases, and when the origin is beyond StreamDistance. When scattering, copy i is offset by angle (i / n) * TAU on the XZ plane — deterministic, no RNG involved.

StaticBake sources

GodotECS.Authoring.StaticBake.BakeFromStatics(Node root) walks a Godot subtree and extracts world-space static colliders for the arcade-physics solver. Returns StaticCollider[] (empty array for a null root). Sources:

  1. CollisionShape3D under a StaticBody3DBoxShape3D → box (half extents scaled by the shape's global scale), SphereShape3D → sphere (radius scaled by max axis), CapsuleShape3D → capsule (radius from XZ scale, height from Y scale). Shapes with no StaticBody3D ancestor, disabled shapes, and shapes with the _baked_disabled meta flag are skipped. The body's CollisionLayer is copied to the collider.
  2. GridMap — every used cell becomes a box centered on the cell's world position with CellSize * 0.5 half extents, using the grid's CollisionLayer.
  3. TileMap — every used cell of layer 0 becomes a flat box (TileSize * 0.5 in X/Y, 0.5 in Z, scaled by the TileMap scale) with layer 1. Requires a TileSet and at least one layer.

Rebake (or flag dirty) whenever a designer moves a wall — the solver never moves these colliders itself. Feed the result into the physics StaticRegistry singleton at scene setup.

Next steps

  • Godot bridge — how baked entities talk back to Godot at runtime.
  • Authoring API — exact signatures for Baker, both authoring nodes, EntitySceneInstance, and StaticBake.

GodotECS docs — version 0.1.0. All rights reserved until a license is added.