Skip to main content

๐Ÿž Lesson 4.4: Baking โ€” From GameObjects to Entities

Creating entities in code is great for spawners and tests, but no designer wants to place a thousand props by typing coordinates. Baking bridges the two worlds: you author with familiar GameObjects in a SubScene, and Unity converts them into runtime entity data at build time. This lesson shows how authoring components and Bakers turn the Editor you know into the ECS data you need.

๐ŸŽฏ Learning Objectives

By the end of this lesson, you will be able to:

  • Explain why baking exists and when it runs (edit/build time, not runtime)
  • Author entities in a SubScene
  • Write an authoring MonoBehaviour with a nested Baker<T>
  • Use GetEntity(TransformUsageFlags) and AddComponent inside Bake
  • Bake an entity prefab and instantiate it at runtime
  • Understand baking dependencies so entities re-bake when the source changes

Estimated Time: 60 minutes  ยท  Prerequisite: Lessons 4.2โ€“4.3 (components, archetypes, systems)

In This Lesson

Why Baking?

ECS wants tightly packed, blittable data with no MonoBehaviour in sight. But authoring a level that way by hand would be miserable โ€” no Inspector, no drag-and-drop, no scene view. Baking resolves the tension: you design with GameObjects, and a one-time conversion turns them into entities.

Crucially, baking happens at edit and build time, not at runtime. There's no per-frame or even per-load conversion cost in a shipping game โ€” by the time the player loads a SubScene, it's already entity data streaming in from disk. Authoring convenience with zero runtime tax. It's the ECS equivalent of importing a texture: friendly source in, optimised runtime format out.

SubScenes

Entities are authored inside a SubScene โ€” a special scene asset nested in your main scene. Right-click in the Hierarchy โ–ธ New Sub Scene โ–ธ Empty Scene, then drag your authoring GameObjects into it. Everything inside a SubScene is baked into entities; everything outside it stays a normal GameObject.

SubScenes also give you streaming: their baked entity data lives in its own file and can be loaded and unloaded as a unit, which is how DOTS games stream huge worlds. For now, think of a SubScene as "the box where GameObjects become entities."

๐Ÿ’ก Closed vs open. A SubScene can be closed (you see the baked entities, read-only, as they'll ship) or open for editing (you see the authoring GameObjects and can move them). Toggle it from the SubScene's header in the Hierarchy while you work.

The Baking Pipeline

The baking pipeline from authoring GameObjects to runtime entities Authoring GameObjects in a SubScene, each with an authoring MonoBehaviour, pass through their Baker classes at edit/build time, producing baked entities with IComponentData that stream into the runtime world. The arrow is labelled edit/build time, and the runtime world is labelled no conversion cost. SubScene (authoring) GameObject: Enemy + VelocityAuthoring (MB) GameObject: Prop + SpinAuthoring (MB) designer-friendly, Inspector-editable Bakers Baker<VelocityAuthoring> Baker<SpinAuthoring> Bake() emits components Runtime entities Entity { LocalTransform, Velocity } no MonoBehaviour, packed in chunks Entity { LocalTransform, Spin } streamed from disk at load no runtime conversion cost โ—„โ€”โ€”โ€” runs at EDIT / BUILD time โ€”โ€”โ€”โ–บ
Figure 1: Baking. Authoring GameObjects (each with an authoring MonoBehaviour) pass through their Bakers at edit/build time and become packed runtime entities โ€” with no conversion cost when the player loads.

Authoring Components & Bakers

The pattern has two parts, usually in one file. The authoring component is an ordinary MonoBehaviour with the fields a designer edits. The nested Baker<T> reads that authoring component and emits the runtime IComponentData:

using Unity.Entities;
using UnityEngine;

// 1) The authoring component: a normal MonoBehaviour, edited in the Inspector.
public class VelocityAuthoring : MonoBehaviour
{
    public Vector3 initialVelocity;

    // 2) The Baker: converts this GameObject into entity data at bake time.
    class Baker : Baker<VelocityAuthoring>
    {
        public override void Bake(VelocityAuthoring authoring)
        {
            // Get the entity this GameObject becomes.
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);

            // Emit the runtime component from the authored value.
            AddComponent(entity, new Velocity { Value = authoring.initialVelocity });
        }
    }
}

Drop VelocityAuthoring onto a GameObject in a SubScene, set its velocity in the Inspector, and the Baker produces an entity carrying a Velocity component. The designer never sees a component struct; the runtime never sees a MonoBehaviour. Note the Bake method runs in the editor โ€” it can freely read the authoring object, do setup, even loop โ€” because none of it ships.

TransformUsageFlags

GetEntity(TransformUsageFlags flags) returns the entity for the baked GameObject, and the flag tells the baker which transform components the entity needs โ€” an optimisation so static props don't carry movement data:

  • TransformUsageFlags.Dynamic โ€” the entity moves at runtime; give it a full LocalTransform. Use for anything a system repositions.
  • TransformUsageFlags.Renderable โ€” it's drawn but never moves; it gets only what rendering needs.
  • TransformUsageFlags.None โ€” it needs no transform at all (e.g. a pure data or singleton entity).

Choosing the tightest flag keeps archetypes lean โ€” a static rock shouldn't drag a writable LocalTransform through cache every frame. When in doubt for something that moves, Dynamic is the safe choice.

Entity Prefabs

Spawning at runtime needs a template โ€” an entity prefab. You bake a GameObject prefab into an entity, store its handle in a component, and a system Instantiates copies. The baker resolves a prefab reference to its baked entity with GetEntity(prefab, flags):

using Unity.Entities;
using UnityEngine;

public struct EnemyPrefab : IComponentData { public Entity Value; }

public class SpawnerAuthoring : MonoBehaviour
{
    public GameObject enemyPrefab;   // a normal prefab dragged in the Inspector

    class Baker : Baker<SpawnerAuthoring>
    {
        public override void Bake(SpawnerAuthoring authoring)
        {
            Entity entity = GetEntity(TransformUsageFlags.None);
            AddComponent(entity, new EnemyPrefab
            {
                // Bake the referenced prefab into its own entity, store the handle.
                Value = GetEntity(authoring.enemyPrefab, TransformUsageFlags.Dynamic)
            });
        }
    }
}

At runtime a system reads the EnemyPrefab singleton and instantiates it โ€” through an EntityCommandBuffer, because Instantiate is a structural change (Lesson 4.3):

var prefab = SystemAPI.GetSingleton<EnemyPrefab>().Value;
var ecb = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>()
                   .CreateCommandBuffer(state.WorldUnmanaged);

Entity spawned = ecb.Instantiate(prefab);
ecb.SetComponent(spawned, LocalTransform.FromPosition(spawnPos));

The instantiated entity is a full copy of the baked prefab's components โ€” the ECS version of Object.Instantiate, and how the mini-project in Lesson 4.5 spawns its swarm.

Baking Dependencies

Baking is incremental โ€” Unity re-bakes only what changed. For that to be correct, a Baker must declare everything it reads beyond the authoring component's own serialized fields. If your Baker reads another asset (a ScriptableObject, a mesh, another component), register it with DependsOn so a change to that asset triggers a re-bake:

public override void Bake(SpawnerAuthoring authoring)
{
    // If we read data off a referenced config asset, depend on it:
    DependsOn(authoring.config);
    var entity = GetEntity(TransformUsageFlags.None);
    AddComponent(entity, new SpawnCount { Value = authoring.config.count });
}

Reading the authoring component's own fields is tracked automatically; it's external reads you must declare. Forget a DependsOn and you'll hit the classic "I changed the config but the entities didn't update until I reopened the SubScene" bug.

โš ๏ธ Bake is editor code โ€” keep it deterministic

A Bake method must produce the same entities for the same inputs every time. Don't use randomness, Time, or frame state in a Baker โ€” bake fixed data, and do any per-run randomisation in a system at runtime. Determinism is what lets incremental baking and version control behave.

Hands-on Challenge

๐Ÿ‹๏ธ Exercise 1: Bake a spinning prop

Objective: Author an entity visually and see it bake.

  1. Create a SubScene and add a Cube GameObject to it.
  2. Write a SpinAuthoring : MonoBehaviour with a float degreesPerSecond and a nested Baker<SpinAuthoring> that adds a Spin component (RadiansPerSecond = math.radians(authoring.degreesPerSecond)) using GetEntity(TransformUsageFlags.Dynamic).
  3. Add SpinAuthoring to the cube, set a value, and close the SubScene.
  4. Open Window โ–ธ Entities โ–ธ Hierarchy and confirm the baked entity has LocalTransform + Spin โ€” then let your SpinSystem from Lesson 4.3 rotate it.
๐Ÿ’ก Hint

You need using Unity.Mathematics; for math.radians. If the entity doesn't appear, check the GameObject is actually inside the SubScene (not just the main scene) and that the SubScene is saved.

๐Ÿ‹๏ธ Exercise 2: Pick the transform flag

Choose a TransformUsageFlags for each baked entity: (a) a boulder that never moves but is rendered; (b) a projectile a system flies across the level; (c) a settings entity holding only a config component and no mesh.

โœ… Answers

(a) Renderable โ€” drawn, never moved, so no writable transform churn. (b) Dynamic โ€” a system repositions it every frame. (c) None โ€” pure data, no transform needed at all.

๐ŸŽฏ Quick Quiz

Question 1: When does baking run?

Question 2: What is an authoring component?

Question 3: Your Baker reads a referenced ScriptableObject, but entities don't update when you edit it. What's missing?

Summary

๐ŸŽ‰ Key Takeaways

  • Baking converts designer-friendly GameObjects into packed runtime entities โ€” at edit/build time, with no runtime cost.
  • You author inside a SubScene; everything in it is baked, everything outside stays a GameObject.
  • An authoring MonoBehaviour holds the Inspector fields; its nested Baker<T> emits IComponentData in Bake().
  • GetEntity(TransformUsageFlags) picks the transform data the entity needs โ€” Dynamic / Renderable / None.
  • Entity prefabs (GetEntity(prefab, flags)) are instantiated at runtime via an EntityCommandBuffer.
  • Declare external reads with DependsOn for correct incremental re-baking, and keep Bake deterministic.

๐Ÿš€ What's Next?

You now have every ECS building block โ€” components, archetypes, systems, jobs, and baking. In Lesson 4.5 you'll combine them into the module's mini-project: baking a prefab, spawning thousands of entities, moving them with a parallel system, and capturing the result โ€” the moving version of Lesson 4.1's opening render.

๐Ÿž Author friendly, ship fast

Design with GameObjects, bake to entities. You keep the Editor you love and the runtime gets pure, packed data โ€” the best of both worlds, resolved before the game ever runs.