A first example of Unity ECS in action

Table of Contents

In this article I will provide a first example on how to get up and running with ECS. If any of the terms make little sense to you jump back to my terms article. For this example you will do nothing super fancy, just a rotating cube. If you don’t want to do all the setup yourself, you can checkout a finished project from GitHub.

Thinking about the data

Thinking about how you can organize your data is crucial for effectively using Unity ECS. The ECS approach works a lot different from the “old” way using GameObjects and MonoBehaviours. So first you need to wrap your thinking around the fact, that behaviour and data are two separate things in ECS. It is quite a departure from the trusty old object oriented approach where you combine data and behaviour into a single object.

In ECS all data is in your components and all behaviour is in systems. With that in mind, let’s think about what data you need for your rotating cube.

  • You need to know where it is in 3D space and how big it is and how its rotation should be.
  • You need to know what mesh and material it should use.
  • You need to know how fast it should rotate.

The good news is, that for the first and second points there are already built-in components from Unity that you can use:

  • The Translation, Scale and Rotation components are there for tracking any entity’s position, size and orientation in 3D space, so you can just use them.
  • There is a RenderMesh component you can use to set the mesh and material. Together with the Translation, Scale and Rotation components you get rendering for free as there is already a built-in system which takes care of rendering all entities that have these components.

Now the only thing that is missing data-wise is the information on how fast the cube should rotate. For this you create a new component:

public struct  RotationSpeed : IComponentData { 
      public float DegreesPerSecond; 
} 

Now you have all the data for building up your entity for a rotating cube.

Building a rotation system

You would like to rotate the cube at a certain speed. This is logic and in ECS systems perform the logic. To keep things simple, start with a ComponentSystem that runs on the main thread:

public class RotationSystem : ComponentSystem 
{ 
    protected override void OnUpdate() 
    { 
         
    } 
} 

A ComponentSystem has an OnUpdate method that is called once per frame, which looks similar to the old MonoBehaviour way of things. However, unlike a MonoBehaviour the system is not bound to any special entity, it can process any entity. So how can you get access to your entities in a component system? If you have read the terms article you may already know the answer - using an EntityQuery. You need an entity query that returns all entities having both a Rotation and a RotationSpeed component. The ComponentSystem class provides a OnCreateManager method that can be used to initialize your system.

private EntityQuery _cubes; 
protected override void OnCreateManager() 
{ 
    _cubes = Entities 
        .WithAll<RotationSpeed, Rotation>() 
        .ToEntityQuery(); 
} 

Here you create the EntityQuery and store it into a field so you can use it in the OnUpdate method:

protected override void OnUpdate() 
{ 
    Entities.With(_cubes) 
        .ForEach((ref RotationSpeed rotationSpeed, ref Rotation rotation) => 
    { 
        rotation.Value = math.mul(rotation.Value, 
            quaternion.RotateY( 
                math.radians( 
                    rotationSpeed.DegreesPerSecond * Time.deltaTime 
                ) 
            ) 
        ); 
    }); 
} 

Now that’s a lot of stuff going on for a simple rotation, so let’s go through it step by step. First there is the Entities.With(_cubes_).ForEach part. This is a convenience built into ComponentSystem which allows you to quickly iterate over all interesting components that are part of a EntityQuery. As parameter you give it a lambda function that is executed for each entity of the EntityQuery - in this case once for each cube in the _cubes component group. The signature of this lambda function tells ForEach in which components you are interested. You are interested in the Rotation and RotationSpeed components. Note that the parameters are having a ref modifier. This is special magic built into this helper method. It allows you to change the component while iterating over it. For this example this is exactly what you need.

You may now scratch your head and wonder why there is so much code for a simple rotation around the Y axis - something, that is in old MonoBehaviour terms as simple as:

 transform.Rotate(Vector3.up, speed * Time.deltaTime); 

The reason for this is, that with ECS Unity has introduced new primitives for representing vectors and quaternions. These are in the Unity.Mathematics library and are very optimized versions of the good old Vector3 and Quaternion classes. Unfortunately they currently lack many convenience features like overloaded operators. So first you have to manually create a quaternion (notice the lower-case class name) representing a rotation around the Y-axis by as many degrees as you need to achieve the desired rotation speed given the elapsed time. The good news is that you can use Time.deltaTime here as this code will run on the main thread. quaternion.RotateY requires a value in radians so you need to call math.radians (notice the lower case class and method names) to convert the degrees to radians.

Finally, you call math.mul to multiply the quaternion representing the entity’s current rotation with the new one you created to get the final rotation. Then you assign the result back to the Rotation component, so the rotation is actually updated.

Making it run

Now with the system and data in place, how do you get this to work in a scene? After all there are no MonoBehaviours involved, so how is your system going to run? The answer to this is straightforward: Unity will run your system automatically. When you start a scene Unity will scan for all ComponentSystems and will automatically instantiate and run them. You can finely control how this works (and even instantiate and run systems yourself if you want to), but for now let’s run with the automatic instantiation. The next question is: How can you create a cube entity that your system will work with? There is more than one way of doing this but for the sake of keeping it simple you’ll use the most straightforward way - Unity’s entity auto-conversion. Using auto conversion you just build a simple game object with MonoBehaviours in your scene and let Unity convert it to entities and components automatically.

So for creating a cube, you can just use Game Object -> 3D Object -> Cube to create a simple cube. Then in the inspector, add a Convert To Entity component to the cube. This will automatically convert the game object to an entity at run time and remove the game object from the scene.

Add a "Convert To Entity" component for automatic conversion.
Add a "Convert To Entity" component for automatic conversion.

This takes care of converting the Transform MonoBehaviour component into Translate, Rotation and Scale ECS components and also creating a shared RenderMesh ECS component for the MeshFilter and MeshRenderer components (yes it’s unfortunate that both the old MonoBehaviour based components and the ECS components are named components).

When you now start the scene, you should see a cube but it’s not rotating. Why is that? Because you didn’t specify the rotation speed yet. Your RotationSpeed component is an ECS component and you cannot add it directly to a GameObject. You can only add MonoBehaviours to GameObjects. So how can you add it to the cube you have in your scene?

For this, you need a little “bridge” MonoBehaviour that wraps our RotationSpeed component and exposes it as a MonoBehaviour that you can add to your cube GameObject in the scene. Unity calls such objects ComponentDataProxy. So create a new class in your project like this:

public class RotationSpeedProxy : ComponentDataProxy<RotationSpeed> 
{ 
} 

All ComponentDataProxy objects are derived from MonoBehaviour so you can now add RotationSpeedProxy to your cube entity and enter a rotation speed:

Add a "Rotation Speed Proxy" component.
Add a "Rotation Speed Proxy" component.

When you now start the scene, you can see the cube rotating. Congratulations, you made your first step into Unity ECS territory. Again, you can check out this project from GitHub and try it for yourself.