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
IsStaticboolfalsetrue → PhysicsStaticTag + PhysicsRadius; false → PhysicsVelocity + 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 streaming gate trips. Distance streaming follows the camera, not the placement origin: the 2-argument SpawnAll measures StreamDistance from the current camera position (GetViewport().GetCamera3D()), so entities spawn when they are near the player wherever you place the ring; pass the 3-argument overload SpawnAll(world, origin, streamOrigin) to pin the streaming origin explicitly (headless/tests). 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 StaticBody3D — BoxShape3D → box, 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, using the grid's CollisionLayer.
  3. TileMap — every used cell of layer 0 becomes a flat box; the collision layer comes from the TileSet's physics layer when configured (else 1). Requires a TileSet and at least one layer.

Rotated or scaled content is handled conservatively: rotated boxes/cells produce a world-aligned AABB via |basis| extents (never under-covers the shape), and tile flips/transposes are resolved. Baking also works before the node is instanced — outside the tree the transforms are composed from the local chain instead of GlobalTransform. 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.2.0. All rights reserved until a license is added.