Entity Component System for Unity: Getting Started
In this Unity tutorial you’ll learn how to efficiently leverage the Entity Component System for more performant gameplay code. By Wilmer Lin.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Entity Component System for Unity: Getting Started
30 mins
- Getting Started
- Stress Testing the Demo
- ECS: Performance by Default
- Removing Non-ECS Code
- Creating an Entity
- Adding Components
- ConvertToEntity
- MoveForward Component Data
- Authoring
- Movement System
- Making an Entity Prefab
- Spawning an Enemy Wave With NativeArray
- Activating the Player
- FacePlayer System
- Generating ComponentTags
- Destruction System
- More Systems and Cleanup
- Where to Go From Here
Authoring
ConvertToEntity converts the EnemyDrone’s transform and rendering information into an equivalent Entity. However, the conversion isn’t automatic for the custom-defined MoveForward. For that you need to add an authoring component.
First, edit the EnemyDrone prefab, (Open Prefab).
Try to drag the MoveForwardComponent onto the prefab. Unity prompts you with an error message.
Now add a [GenerateAuthoringComponent]
attribute to the top of the MoveForward struct:
[GenerateAuthoringComponent]
Next, drag the modified script onto the EnemyDrone again. This time Unity attaches a Monobehaviour called MoveForwardAuthoring. Any public fields from MoveForward will now appear in the Inspector.
Change speed
to 5 and save changes to the prefab.
Now, enter Play mode and confirm that the authoring component set the Component data default value in the EntityDebugger. Your Entity should have a MoveForward data type with a speed value of 5.
Movement System
Your Entity has some Component data now, but data can’t do anything by itself. To make it move, you need to create a System.
In Scripts/ECS/Systems, make a new C# script called MovementSystem:
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
// 1
public class MovementSystem : ComponentSystem
{
// 2
protected override void OnUpdate()
{
// 3
Entities.WithAll<MoveForward>().ForEach((ref Translation trans, ref Rotation rot, ref MoveForward moveForward) =>
{
// 4
trans.Value += moveForward.speed * Time.DeltaTime * math.forward(rot.Value);
});
}
}
This represents a basic System in ECS:
The WithAll
works as a filter. This restricts the loop to Entities that have MoveForward data. You only have one Entity at the moment, but this will be significant later.
The argument for the ForEach
is a lambda function, which takes the form of:
Use the ref
keyword in front of input parameters. In this example, you pass in references to the Translation, Rotation and MoveForward component data.
Each Entity increments its position by this amount and voila! It moves in its local positive z-direction.
-
MovementSystem
inherits fromComponentSystem
, which is an abstract class needed to implement a system. - This must implement
protected override void OnUpdate()
, which invokes every frame. - Use
Entities
with a staticForEach
to run logic over every Entity in the World.The
WithAll
works as a filter. This restricts the loop to Entities that have MoveForward data. You only have one Entity at the moment, but this will be significant later.The argument for the
ForEach
is a lambda function, which takes the form of:(input parameters) => {expression}Use the
ref
keyword in front of input parameters. In this example, you pass in references to the Translation, Rotation and MoveForward component data. - The lambda expression calculates the speed relative to one frame,
moveForward.speed * Time.DeltaTime
. Then, it multiplies that by the local forward vector,(math.forward(rot.Value)
.
Each Entity increments its position by this amount and voila! It moves in its local positive z-direction.
Once you save the file, the System is active. There’s no need to attach it to anything in the Hierarchy. It runs whether you want it to or not!
Now, enter Play mode and… success! Your drone flies in a straight line!
Experiment with different y-rotation values and speed values on your EnemyDrone.
Making an Entity Prefab
So far, you’ve created a single enemy Entity, but eventually, you’ll want to make more. You can define an Entity as a reusable prefab. Then at runtime, you can create as many Entities as you see fit.
First, add these fields to the top of EnemySpawner.cs:
[SerializeField] private GameObject enemyPrefab;
private Entity enemyEntityPrefab;
Then drop these lines at the bottom of Start
:
var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
enemyEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(enemyPrefab, settings);
Wow, that’s a mouthful! Though the syntax is a bit verbose, this merely sets up some default conversion settings. Then it passes those settings into GameObjectConversionUtility.ConvertGameObjectHierarchy
.
The result is an Entity prefab that you can build at runtime.
To test this, follow these steps. First, delete EnemyDrone from the Hierarchy by selecting right-click ► Delete and then select EnemySpawner. In the Inspector, drag the EnemyDrone from Prefabs into EnemyPrefab.
At runtime, nothing appears yet. You must instantiate the Entity prefab to see it.
So, append this line to Start
:
entityManager.Instantiate(enemyEntityPrefab);
Now enter Play mode. Once again, your single enemy drone reappears and flies forward. This time, it starts from the prefab’s default position.
Now you can create instances of an Entity, as you would with GameObject prefabs. Your goal is to create more than one enemy, so comment out this line and invoke SpawnWave instead.
// entityManager.Instantiate(enemyEntityPrefab);
SpawnWave();
Next, you’ll fill out the logic for SpawnWave
and create an enemy swarm.
Spawning an Enemy Wave With NativeArray
While one drone is fun, a whole swarm of drones is better. :]
To create your swarm, first add a new using
line to the top of EnemySpawner.cs:
using Unity.Collections;
This gives you access to a special collection type called NativeArray. NativeArrays can loop through Entities with less memory overhead.
Then, fill in SpawnWave
with the following code to randomly place an array of Entities in a circle formation:
private void SpawnWave()
{
// 1
NativeArray<Entity> enemyArray = new NativeArray<Entity>(spawnCount, Allocator.Temp);
// 2
for (int i = 0; i < enemyArray.Length; i++)
{
enemyArray[i] = entityManager.Instantiate(enemyEntityPrefab);
// 3
entityManager.SetComponentData(enemyArray[i], new Translation { Value = RandomPointOnCircle(spawnRadius) });
// 4
entityManager.SetComponentData(enemyArray[i], new MoveForward { speed = Random.Range(minSpeed, maxSpeed) });
}
// 5
enemyArray.Dispose();
// 6
spawnCount += difficultyBonus;
}
Here's whats happening in SpawnWave
:
- First, you declare a new NativeArray,
enemyArray
, with up to thespawnCount
elements.Allocator.Temp
indicates the NativeArray won’t need to persist once the setup is complete. - Next, you loop through the array. Each iteration instantiates an Entity and stores it in
enemyArray
. - Then, you find a 3D position using
RandomPointOnCircle
with thespawnRadius.
That plugs into the Translation value withSetComponentData
. - You use
SetComponentData
to set the MoveForward speed to aRandom.Range
betweenminSpeed
andmaxSpeed
. - Once the loop has finished,
NativeArray.Dispose
frees any temporarily allocated memory. - Finally, you can increment the
spawnCount
on each wave to make the game progressively harder. Keep your players on their toes!
In Play mode, the Scene view now shows a ring of enemy drones. The enemies spawn and move in the positive z-direction.
Adjust the Enemy Spawner’s Spawn Count, and Spawn Radius to control the timing and density of the drones.