๐ 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)andAddComponentinsideBake - 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
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 fullLocalTransform. 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.
- Create a SubScene and add a Cube GameObject to it.
- Write a
SpinAuthoring : MonoBehaviourwith afloat degreesPerSecondand a nestedBaker<SpinAuthoring>that adds aSpincomponent (RadiansPerSecond = math.radians(authoring.degreesPerSecond)) usingGetEntity(TransformUsageFlags.Dynamic). - Add
SpinAuthoringto the cube, set a value, and close the SubScene. - Open Window โธ Entities โธ Hierarchy and confirm the baked entity has
LocalTransform+Spinโ then let yourSpinSystemfrom 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>emitsIComponentDatainBake(). GetEntity(TransformUsageFlags)picks the transform data the entity needs โDynamic/Renderable/None.- Entity prefabs (
GetEntity(prefab, flags)) are instantiated at runtime via anEntityCommandBuffer. - Declare external reads with
DependsOnfor correct incremental re-baking, and keepBakedeterministic.
๐ 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.