๐ฆ Lesson 4.2: Components & Archetypes
Lesson 4.1 gave you the mental model; now we make it real. You'll install the Entities package, define your own IComponentData structs, and create entities in code with the EntityManager โ then watch the archetype/chunk system organise them in memory exactly as the diagrams promised. This is the concrete foundation for the systems and baking that follow.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Install and enable the Entities package family
- Define data components (
IComponentData) and zero-size tag components - Create entities and add components with the
EntityManager - Create archetypes explicitly and batch-instantiate entities
- Explain how chunks store entities and why structural changes cost
- Use enableable components to toggle state without structural churn
Estimated Time: 60 minutes ยท Prerequisite: Lesson 4.1 (The ECS Mindset)
In This Lesson
Installing Entities
ECS lives in the Entities package. Open Window โธ Package Manager, and install Entities (it pulls in Burst, Collections, and Mathematics from Module 3 as dependencies). To render entities you also want Entities Graphics, which adds the hybrid renderer that draws entities through URP. Installing Entities Graphics is the simplest route โ it brings Entities along with it.
After install you'll see new editor windows under Window โธ Entities (Hierarchy, Systems, Components) and DOTS options in the project settings. Those windows are UI Toolkit panels โ throughout this module we describe and diagram them rather than screenshot them, exactly as with the other node-editor windows in the course.
โ ๏ธ Entities is a big commitment
The Entities package changes how part of your project is built (source generators, baking, its own assemblies). Add it to a project that intends to use ECS โ not on a whim to a shipping GameObject game. That's why Lesson 4.1 stressed choosing ECS for the parts that need scale, and keeping GameObjects for the rest.
Defining Components
A component is a struct implementing IComponentData, holding only blittable data. Here are a couple we'll use โ note the Unity.Mathematics types from Module 3:
using Unity.Entities;
using Unity.Mathematics;
// Pure data. No methods, no MonoBehaviour, no references.
public struct Velocity : IComponentData
{
public float3 Value;
}
public struct Health : IComponentData
{
public int Current;
public int Max;
}
These join the built-in components you already met: Unity.Transforms.LocalTransform (position/rotation/scale) and the rendering components that Entities Graphics attaches. Every field must be blittable โ no string (use FixedString), no class references, no managed arrays. If you need a variable-length list per entity, there's a special DynamicBuffer element type, but plain IComponentData covers most data.
Creating Entities in Code
Entities live in a World, and the EntityManager is the API for creating and editing them. In play mode the default world already exists:
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
EntityManager em = World.DefaultGameObjectInjectionWorld.EntityManager;
// Create one empty entity, then add components (each Add is a structural change).
Entity e = em.CreateEntity();
em.AddComponentData(e, LocalTransform.FromPosition(new float3(0, 1, 0)));
em.AddComponentData(e, new Velocity { Value = new float3(1, 0, 0) });
em.AddComponentData(e, new Health { Current = 100, Max = 100 });
em.AddComponent<Enemy>(e); // add a tag (no data)
// Read and write component data:
Velocity v = em.GetComponentData<Velocity>(e);
em.SetComponentData(e, new Velocity { Value = v.Value * 2f });
That's the imperative way to build entities โ handy for spawners and tests. In production you'll usually author entities with GameObjects and bake them (Lesson 4.4), but doing it by hand here makes the data model concrete: an entity is an ID, and you attach typed data to it.
๐ก Where does the code run? You can call theEntityManagerfrom aMonoBehaviour(to bootstrap a scene) or, more idiomatically, from inside a system โ which is Lesson 4.3. Either way it's the same manager editing the same world.
Archetypes Explicitly
Adding components one by one works, but each add is a structural change that moves the entity between archetypes. When you're creating many identical entities, it's far better to declare the archetype up front and create them all at once:
// Declare the exact component set once.
EntityArchetype archetype = em.CreateArchetype(
typeof(LocalTransform),
typeof(Velocity),
typeof(Enemy)
);
// Batch-create 10,000 entities of that archetype in one call โ no per-entity churn.
NativeArray<Entity> enemies = em.CreateEntity(archetype, 10000, Allocator.Temp);
for (int i = 0; i < enemies.Length; i++)
{
em.SetComponentData(enemies[i], LocalTransform.FromPosition(RandomPos(i)));
em.SetComponentData(enemies[i], new Velocity { Value = RandomDir(i) });
}
enemies.Dispose(); // the array of handles; the entities live on in the world
All 10,000 entities share one archetype, so they pack into the same chunks and any system that queries { LocalTransform, Velocity, Enemy } streams straight through them. This is precisely how the 25,600-entity render in Lesson 4.1 was built โ one archetype, one batch instantiate, then set each entity's data.
Inside a Chunk
Recall from Lesson 4.1 that entities of one archetype are stored together in 16 KB chunks, each component type as its own packed array. How many entities fit in a chunk depends on the archetype's total size per entity โ a lean archetype packs more per chunk (better cache use); a fat one packs fewer.
Structural Changes & Enableable Components
Adding a component, removing one, creating or destroying an entity โ these are structural changes. Because they move an entity between chunks (or allocate/free chunk space), they're heavier than editing a value, and they can't happen safely from inside a running job. That's why bulk creation uses archetypes, and why systems batch structural changes through an EntityCommandBuffer (Lesson 4.3).
But sometimes you want to toggle state constantly โ stun an enemy this frame, un-stun it next โ and adding/removing a tag every time would thrash the chunks. The fix is an enableable component: a component you can switch on and off without a structural change.
using Unity.Entities;
// IEnableableComponent lets you toggle presence cheaply โ no chunk move.
public struct Stunned : IComponentData, IEnableableComponent { }
// Toggle it like a flag; queries can filter on enabled/disabled state.
em.SetComponentEnabled<Stunned>(entity, true); // stun
em.SetComponentEnabled<Stunned>(entity, false); // recover
The entity keeps the component in its archetype the whole time; only a bit flips. Queries can be told to consider only entities where Stunned is enabled. Reach for enableable components whenever a flag changes often โ it keeps the memory layout stable and avoids structural-change cost on the hot path.
โ Rule of thumb
Use a plain tag when the category rarely changes (an entity is an Enemy for its whole life). Use an enableable component when a flag flips frequently (Stunned, Targeted, Highlighted). Both are queryable; only one is cheap to toggle every frame.
Hands-on Challenge
๐๏ธ Exercise 1: Spawn a grid of entities in code
Objective: Create entities by hand and confirm they exist.
- Define
Velocity : IComponentDatawith afloat3 Value, and aMover : IComponentDatatag. - In a MonoBehaviour's
Start, get the default world'sEntityManagerandCreateArchetype(typeof(LocalTransform), typeof(Velocity), typeof(Mover)). - Batch-create 1,000 entities of that archetype; set each one's
LocalTransformto a random position and give it a randomVelocity. - Open Window โธ Entities โธ Hierarchy in play mode and confirm 1,000 entities exist with those components.
๐ก Hint
Use em.CreateEntity(archetype, 1000, Allocator.Temp) to get a NativeArray<Entity>, loop with SetComponentData, then Dispose() the array (not the entities). Rendering them is Lesson 4.5 โ for now the Entities Hierarchy is your proof they were created.
๐๏ธ Exercise 2: Tag vs enableable
For each, choose a plain tag or an enableable component: (a) an entity is a Projectile for its whole short life; (b) a unit is Selected, toggled many times a second as the player boxes-selects; (c) an entity is on the PlayerTeam.
โ Answers
(a) plain tag โ set once at spawn. (b) enableable โ flips constantly, avoid structural churn. (c) plain tag โ team rarely changes. Only (b) benefits from IEnableableComponent.
๐ฏ Quick Quiz
Question 1: What is a tag component?
Question 2: Why prefer CreateArchetype + batch create over adding components one at a time to 10,000 entities?
Question 3: A Stunned flag flips on and off many times per second. Best choice?
Summary
๐ Key Takeaways
- The Entities package (with Entities Graphics to render) brings ECS, pulling in Burst/Collections/Mathematics.
- Components are blittable
IComponentDatastructs; a zero-field one is a tag that systems query by presence. - The
EntityManagercreates entities and adds/gets/sets components; batch-create with an archetype for scale. - An archetype owns its chunks; a query matches every archetype containing its components and streams their chunks.
- Structural changes (add/remove/create/destroy) move entities and cost more than editing values.
- Enableable components toggle a flag without a structural change โ ideal for frequently-flipped state.
๐ What's Next?
You can build ECS data; now you need behaviour. In Lesson 4.3: Systems & Queries โ ISystem & SystemAPI we write systems that query these components every frame, edit them in bulk, and (with IJobEntity) run that work as Burst-compiled parallel jobs.
๐ฆ Data, in place
Components are your columns, archetypes are your table schemas, chunks are the pages. You now lay that data out deliberately โ the systems in the next lesson simply run over it.