Spawn and Move Prefabs
Full workflows and code to programmatically spawn, update, and destroy prefab entities

What you'll develop on this page

Preview of result of this section
Use game settings to programmatically spawn asteroid prefabs to create an asteroid field (cube).

Spawning asteroid prefabs

First, some background

An entity prefab is nothing more than an entity with a Prefab tag and a LinkedEntityGroup. The former identifies the prefab and makes it invisible to all entity queries but the ones who explicitly include prefabs, and the latter links together a set of entities, since entity prefabs can be complex assemblies (equivalent to GameObject hierarchies).
So the following two components are equivalent, one in classic Unity and the other in DOTS.
1
// Authoring component
2
public class PrefabReference : MonoBehaviour
3
{
4
public GameObject Prefab;
5
}
6
7
// Runtime component
8
public struct PrefabEntityReference : IComponentData
9
{
10
public Entity Prefab;
11
}
Copied!
By default, the conversion workflow only processes the actual contents of an authoring scene, so a specific mechanism is required to also include prefabs from the asset folder. This is the purpose of the system group GameObjectDeclareReferencedObjectsGroup, it runs before the primary entities are created in the destination world, and provides a way of registering prefabs for conversion.

Setting up a prefab with ECS

    Click the little down arrow under Hierarchy, choose "3D Object" > "Sphere"
    Drag the GameObject into the Scripts and Prefabs folder then delete the GameObject from the hierarchy (right click Delete is better then hitting the delete key)
    Click on the sphere in the Project window, then click "Open Prefab" in the Inspector
    Rename it to "Asteroid"
    Adjust the scale to (2,2,2)
    Remove the "Sphere Collider" component (click three vertical dots menu to find Remove)
      This is a MonoBehaviour collider, and in the next section, DOTS Physics, we will replace this collider with DOTS collider
    In Scripts and Prefabs Create "AsteroidTag" runtime component (Create > ECS > Runtime Component Type)
      This will be IComponentData but will not contain any data within it
      When creating components with no data, the convention is to append the name with "Tag" to it make clear
        Although this isn't necessary, it tends to help you remember how components are meant to be used
    AsteroidTag.cs
1
using Unity.Entities;
2
3
public struct AsteroidTag : IComponentData
4
{
5
}
6
Copied!
Create the asteroid prefab and create the AsteroidTag component. Deleted the asteroid from the scene Hierarchy post-gif
    Try to put the AsteroidTag on the asteroid prefab
Trying to put the AsteroidTag on the Asteroid prefab causes an error
    Trying to put the AsteroidTag on the asteroid prefab causes an error: "Can't add script behavior to AsteroidTag. The script needs to derive from MonoBehaviour."
      Because the Editor is not ECS, you cannot add IComponentData to a prefab before runtime
      Even though it "seems" like you should, it is important to remember that the Asteroid is currently a GameObject and so putting a runtime ECS component on it actually doesn't make sense, what we need to do is make AsteroidTag an "authoring component"
For simple runtime components, the GenerateAuthoringComponent attribute can be used to request the automatic creation of an authoring component for a runtime component. You can then add the script containing the runtime component directly to a GameObject within the Editor.
From ECS Conversion Workflow documentation "Generated authoring components" section
    Add a [GenerateAuthoringComponent] decorator to the AsteroidTag
1
using Unity.Entities;
2
3
[GenerateAuthoringComponent]
4
public struct AsteroidTag : IComponentData
5
{
6
}
Copied!
    Now try to put the AsteroidTag on the Asteroid prefab again (select Asteroid in Hierarchy > Click "Add Component" in the Inspector)
After adding [GenerateAuthoringComponent] decorator, you are able to add the AsteroidTag to prefab
    Now we have an Asteroid entity with an AsteroidTag authoring component. This "tag" enables us to "find" asteroid entities, by checking whether or not entities have an AsteroidTag component
    How do we "refer" to the "asteroid" prefab when we want to create asteroids? We need a way to "reference" this prefab when writing our ECS.
      Create an empty GameObject in ConvertedSubScene named PrefabCollection
      This GameObject will hold all the references to our prefabs
      When the SubScene converts this GameObject into an entity, we will refer to the PrefabCollection entity's components to reference our prefabs
    Create AsteroidAuthoringComponent (right click Scripts and Prefabs > Create > ECS > Authoring Component Type)
1
using Unity.Entities;
2
3
[GenerateAuthoringComponent]
4
public struct AsteroidAuthoringComponent : IComponentData
5
{
6
public Entity Prefab;
7
}
Copied!
    Go back and select the PrefabCollection GameObject in Hierarchy and add AsteroidAuthoringComponent (click Add Component in Inspector)
    Drag the asteroid prefab from the Project window into the public Prefab field under the script component in Inspector
    Save the SubScene then click "Reimport" in Inspector to make sure assets are reimported into SubScene
    We now have a reference to our asteroid prefab
Create Prefab collection and add AsteroidAuthoringComponent to it
    We will be using the following workflow to reference prefabs for the remainder of this gitbook:
      Create a "PrefabAuthoringComponent" with an Entity field
      Add it to our PrefabCollection GameObject in our SubScene
      Drag our prefab from the project window into the Entity field of the authoring component
      We will be able to reference the authoring component to instantiate our prefabs

Creating a spawning system

    Navigate to "Scripts and Prefabs", right click and select Create > ECS > System and name the system "AsteroidSpawnSystem"
      "System" (the S in ECS)
Unity ECS automatically discovers system classes in your project and instantiates them at runtime. It adds each discovered system to one of the default system groups. You can use system attributes to specify the parent group of a system and the order of that system within the group . If you do not specify a parent, Unity adds the system to the Simulation system group of the default world in a deterministic, but unspecified, order. You can also use an attribute to disable automatic creation.
    Hit the "play" button and check out the Entity Debugger
      Our AsteroidSpawnSystem is automatically placed into the Simulation System Group
        We are able to change which SystemGroup our system runs in by adding decorators to our system
      Click on the AsteroidSpawnSystem and you will see that our system interacts with 2 components, Rotation and Translation
        These are default components in the boilerplate system that are used by the EntityQuery (the EntityQuery "checks" for these components)
    AsteroidSpawnSystem.cs 's boilerplate code is below
      We can see from the boilerplate code that there is a reference to "Translation" and "Rotation" which was picked up in the Debugger
1
using Unity.Burst;
2
using Unity.Collections;
3
using Unity.Entities;
4
using Unity.Jobs;
5
using Unity.Mathematics;
6
using Unity.Transforms;
7
public class AsteroidSpawnSystem : SystemBase
8
{
9
protected override void OnUpdate()
10
{
11
// Assign values to local variables captured in your job here, so that it has
12
// everything it needs to do its work when it runs later.
13
// For example,
14
// float deltaTime = Time.DeltaTime;
15
// This declares a new kind of job, which is a unit of work to do.
16
// The job is declared as an Entities.ForEach with the target components as parameters,
17
// meaning it will process all entities in the world that have both
18
// Translation and Rotation components. Change it to process the component
19
// types you want.
20
Entities.ForEach((ref Translation translation, in Rotation rotation) => {
21
// Implement the work to perform for each entity here.
22
// You should only access data that is local or that is a
23
// field on this job. Note that the 'rotation' parameter is
24
// marked as 'in', which means it cannot be modified,
25
// but allows this job to run in parallel with other jobs
26
// that want to read Rotation component data.
27
// For example,
28
// translation.Value += math.mul(rotation.Value, new float3(0, 0, 1)) * deltaTime;
29
}).Schedule();
30
}
31
}
Copied!
Unity ECS provides several types of systems. In general, the systems you write to implement your game behavior and data transformations will extend SystemBase. The other system classes have specialized purposes. You typically use existing instances of the EntityCommandBufferSystem and ComponentSystemGroup classes.
    SystemBase -- the base class to implement when creating systems.
    EntityCommandBufferSystem -- provides EntityCommandBuffer instances for other systems. Each of the default system groups maintains an Entity Command Buffer System at the beginning and end of its list of child systems. This allows you to group structural changes so that they incur fewer synchronization points in a frame.
    ComponentSystemGroup -- provides nested organization and update order for other systems. Unity ECS creates several Component System Groups by default.
    GameObjectConversionSystem -- converts GameObject-based, in-Editor representations of your game to efficient, entity-based, runtime representations. Game conversion systems run in the Unity Editor.
Let's briefly take a look at the system types referenced in the documentation.
SystemBase - this is the type of system we will be using most of the time for our runtime game play. The systems of this type will do things like provide movement to asteroids or Spawn/Destroy.
ComponentSystemGroup - this is groupings of systems.
From https://docs.unity3d.com/Packages/[email protected]/manual/system_update_order.html
GameObjectConversionSystem - We interacted with systems of this type when working with our GameSettingsComponent and our SetGameSettingsSystem. These are used for hybrid development.
EntityCommandBufferSystem - So far we have not used this type of system. The systems in EntityCommandBufferSystem are used as "sync" points for structural changes made to entities. You can see these systems in the "Default System Groups" image above. There is a Begin{SystemGroup}EntityCommandBufferSystem and End{SystemGroup}EntityCommandBufferSystem for each of the 3 main default system groups (InitializationSystemGroup, SimulationSystemGroup, and PresentationSystemGroup). If confused about "structural" changes it is good to refer to Unity's overview of ECS concepts.
An Entity is a collection of components. Let's say there is an asteroid with a Translation component which is a float3 and denotes the asteroid's position in space. Updating the Entity's Translation component values is not a structural change. The Entity still has the same amount and types of components, just different values.
A unique combination of component types is called an EntityArchetype. For example, a 3D object might have a component for its world transform, one for its linear movement, one for rotation, and one for its visual representation. Each instance of one of these 3D objects corresponds to a single entity, but because they share the same set of components, ECS classifies them as a single archetype:
In this diagram, entities A and B share archetype M, while entity C has archetype N
Let's say we add a "DestroyTag" to the asteroid Entity. That will actually "change" the EntityArchetype it is (because it has a different collection of components). Structural changes are computationally more expensive than just updating values.
Sync points are caused by operations that you cannot safely perform when there are any other jobs that operate on components. Structural changes to the data in ECS are the primary cause of sync points. All of the following are structural changes:
    Creating entities
    Deleting entities
    Adding components to an entity
    Removing components from an entity
    Changing the value of shared components
Broadly speaking, any operation that changes the archetype of an entity or causes the order of entities within a chunk to change is a structural change. These structural changes can only be performed on the main thread.
You can use entity command buffers (ECBs) to queue up structural changes instead of immediately performing them. Commands stored in an ECB can be played back at a later point during the frame. This reduces multiple sync points spread across the frame to a single sync point when the ECB is played back.
Each of the standard ComponentSystemGroup instances provides a EntityCommandBufferSystem as the first and last systems updated in the group. By getting an ECB object from one of these standard ECB systems, all structural changes within the group occur at the same point in the frame, resulting in one sync point rather than several. ECBs also allow you to record structural changes within a job. Without an ECB, you can only make structural changes on the main thread. (Even on the main thread, it is typically faster to record commands in an ECB and then play back those commands, than it is to make the structural changes one-by-one using the EntityManager class itself.)
If sync points and EntityCommandBuffers are making your head hurt, you should really watch Unity's talk on Entity Command Buffers: https://www.youtube.com/watch?v=SecJibpoTYw <-- Worth the watch.
We will be using the BeginSimulationEntityCommandBuffer in our AsteroidSpawnSystem. This command buffer plays back our structural changes (creating asteroid entities).
We will use a Job.WithCode() to generate our asteroids.
The Job.WithCode construction provided by the SystemBase class is an easy way to run a function as a single background job. You can also run Job.WithCode on the main thread and still take advantage of Burst compilation to speed up execution.
You cannot pass parameters to the Job.WithCode lambda function or return a value. Instead, you can capture local variables in your OnUpdate() function.
When you schedule your job to run in the C# Job System using Schedule(), there are additional restrictions:
    Captured variables must be declared as NativeArray -- or other native container -- or a blittable type.
    To return data, you must write the return value to a captured native array, even if the data is a single value. (Note that you can write to any captured variable when executing with Run().)
Job.WithCode provides a set of functions to apply read-only and safety attributes to your captured native container variables. For example, you can use WithReadOnly to designate that you don't update the container and WithDisposeOnCompletion to automatically dispose a container after the job finishes. (Entities.ForEach provides the same functions.)
See Job.WithCode for more information about these modifiers and attributes.
Think of Job.WithCode as a super-hardcore ECS "for loop" in an Update() function.
    Make AsteroidSpawnSystem and paste this code snippet into AsteroidSpawnSystem.cs:
1
using System.Diagnostics;
2
using Unity.Entities;
3
using Unity.Collections;
4
using Unity.Jobs;
5
using Unity.Mathematics;
6
using Unity.Transforms;
7
using UnityEngine;
8
using Unity.Burst;
9
10
public class AsteroidSpawnSystem : SystemBase
11
{
12
//This will be our query for Asteroids
13
private EntityQuery m_AsteroidQuery;
14
15
//We will use the BeginSimulationEntityCommandBufferSystem for our structural changes
16
private BeginSimulationEntityCommandBufferSystem m_BeginSimECB;
17
18
//This will be our query to find GameSettingsComponent data to know how many and where to spawn Asteroids
19
private EntityQuery m_GameSettingsQuery;
20
21
//This will save our Asteroid prefab to be used to spawn Asteroids
22
private Entity m_Prefab;
23
24
protected override void OnCreate()
25
{
26
//This is an EntityQuery for our Asteroids, they must have an AsteroidTag
27
m_AsteroidQuery = GetEntityQuery(ComponentType.ReadWrite<AsteroidTag>());
28
29
//This will grab the BeginSimulationEntityCommandBuffer system to be used in OnUpdate
30
m_BeginSimECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
31
32
//This is an EntityQuery for the GameSettingsComponent which will drive how many Asteroids we spawn
33
m_GameSettingsQuery = GetEntityQuery(ComponentType.ReadWrite<GameSettingsComponent>());
34
35
//This says "do not go to the OnUpdate method until an entity exists that meets this query"
36
//We are using GameObjectConversion to create our GameSettingsComponent so we need to make sure
37
//The conversion process is complete before continuing
38
RequireForUpdate(m_GameSettingsQuery);
39
}
40
41
protected override void OnUpdate()
42
{
43
//Here we set the prefab we will use
44
if (m_Prefab == Entity.Null)
45
{
46
//We grab the converted PrefabCollection Entity's AsteroidAuthoringComponent
47
//and set m_Prefab to its Prefab value
48
m_Prefab = GetSingleton<AsteroidAuthoringComponent>().Prefab;
49
50
//we must "return" after setting this prefab because if we were to continue into the Job
51
//we would run into errors because the variable was JUST set (ECS funny business)
52
//comment out return and see the error
53
return;
54
}
55
56
//Because of how ECS works we must declare local variables that will be used within the job
57
//You cannot "GetSingleton<GameSettingsComponent>()" from within the job, must be declared outside
58
var settings = GetSingleton<GameSettingsComponent>();
59
60
//Here we create our commandBuffer where we will "record" our structural changes (creating an Asteroid)
61
var commandBuffer = m_BeginSimECB.CreateCommandBuffer();
62
63
//This provides the current amount of Asteroids in the EntityQuery
64
var count = m_AsteroidQuery.CalculateEntityCountWithoutFiltering();
65
66
//We must declare our prefab as a local variable (ECS funny business)
67
var asteroidPrefab = m_Prefab;
68
69
//We will use this to generate random positions
70
var rand = new Unity.Mathematics.Random((uint)Stopwatch.GetTimestamp());
71
72
Job
73
.WithCode(() => {
74
for (int i = count; i < settings.numAsteroids; ++i)
75
{
76
// this is how much within perimeter asteroids start
77
var padding = 0.1f;
78
79
// we are going to have the asteroids start on the perimeter of the level
80
// choose the x, y, z coordinate of perimeter
81
// so the x value must be from negative levelWidth/2 to positive levelWidth/2 (within padding)
82
var xPosition = rand.NextFloat(-1f*((settings.levelWidth)/2-padding), (settings.levelWidth)/2-padding);
83
// so the y value must be from negative levelHeight/2 to positive levelHeight/2 (within padding)
84
var yPosition = rand.NextFloat(-1f*((settings.levelHeight)/2-padding), (settings.levelHeight)/2-padding);
85
// so the z value must be from negative levelDepth/2 to positive levelDepth/2 (within padding)
86
var zPosition = rand.NextFloat(-1f*((settings.levelDepth)/2-padding), (settings.levelDepth)/2-padding);
87
88
//We now have xPosition, yPostiion, zPosition in the necessary range
89
//With "chooseFace" we will decide which face of the cube the Asteroid will spawn on
90
var chooseFace = rand.NextFloat(0,6);
91
92
//Based on what face was chosen, we x, y or z to a perimeter value
93
//(not important to learn ECS, just a way to make an interesting prespawned shape)
94
if (chooseFace < 1) {xPosition = -1*((settings.levelWidth)/2-padding);}
95
else if (chooseFace < 2) {xPosition = (settings.levelWidth)/2-padding;}
96
else if (chooseFace < 3) {yPosition = -1*((settings.levelHeight)/2-padding);}
97
else if (chooseFace < 4) {yPosition = (settings.levelHeight)/2-padding;}
98
else if (chooseFace < 5) {zPosition = -1*((settings.levelDepth)/2-padding);}
99
else if (chooseFace < 6) {zPosition = (settings.levelDepth)/2-padding;}
100
101
//we then create a new translation component with the randomly generated x, y, and z values
102
var pos = new Translation{Value = new float3(xPosition, yPosition, zPosition)};
103
104
//on our command buffer we record creating an entity from our Asteroid prefab
105
var e = commandBuffer.Instantiate(asteroidPrefab);
106
107
//we then set the Translation component of the Asteroid prefab equal to our new translation component
108
commandBuffer.SetComponent(e, pos);
109
}
110
}).Schedule();
111
112
//This will add our dependency to be played back on the BeginSimulationEntityCommandBuffer
113
m_BeginSimECB.AddJobHandleForProducer(Dependency);
114
}
115
}
Copied!
    Read through the comments to get a line-by-line understanding of what is going on
Important Note: A quirk of ECS is that you must declare local variables in the OnUpdate method if you want to use that data in a Job that runs in OnUpdate().
This means that if you want to use certain variables in your OnUpdate(), and you already declared them in your OnCreate(), you must still also declare variables locally in OnUpdate().
Is there any point to declaring variables in OnCreate()? Yes, there is! If you grab the variable from OnCreate(), you only reach "out" once during runtime. But, if you are calling for the value from OnUpdate(), that means you reach "out" for the local variable on each OnUpdate().
    You'll notice the general set-up of an ECS system is broken down into two major parts:
      OnCreate
        EntityQueries, CommandBuffers, RequireForUpdates
      OnUpdate
        Setup your local variables
        Run your Job.WithCode() or Entities.ForEach()
OnCreate
    Here we set up EntityQueries on the entities and components we need
      We will need to know how many Asteroids exist so we can spawn the right amount
      We need data from our GameSettingsComponent, so we create a query and see if it exists before we continue to our OnUpdate
    Here we also set up our EntityCommandBuffer which will be used to record structural changes
OnUpdate
    Here we set our prefab by grabbing the data from our AsteroidAuthoringComponent
    We declare the local variables we will need for our Job.WithCode()
    We create a Translation that is on the perimeter of a cube defined by our levelHeight, levelWidth, and levelDepth in our GameSettingsComponent
    We instantiate a new entity from our prefab and set its Translation component to our translation
Important note: The reason why we are able to easily pass our GameSettingsComponent data into the Job.WithCode() is because the entity with that data is a Singleton. A Singleton is an entity that is the only entity that exists with this particular component.
What if we wanted to pass in PlayerComponent data to the Job.WithCode() and there could be multiple players active during the OnUpdate()?
In this case you would use:
m_PlayersQuery = GetEntityQuery(ComponentType.ReadWrite<PlayerComponent>()); JobHandle playersDep; var players = m_PlayersQuery.ToComponentDataArrayAsync(Allocator.TempJob, out playersDep);
    This creates a NativeArray of Player component data
    This array can now be referenced within a Job
    Now there is an additional dependency to create that NativeArray which also must be disposed, this is where it gets tricky
    You will need to combine dependencies to ensure there are no race conditions
Using NativeArrays will be discussed in future sections. So why are we confusing you by bringing up this additional information that isn't needed right now? That's because introducing future workflows to you now might help you build a better mental model of how to navigate ECS.
You might have noticed our AsteroidSpawnSystem ends with a .Schedule() which means we are running a a single job. A great benefit of Unity DOTS is the ability to schedule parallel jobs to make the most of the targeting compute platform. If we wanted to make AsteroidSpawnSystem run parallel jobs we would need to use an IJobFor.
Note: To run a parallel job, implement IJobFor, which you can schedule using ScheduleParallel() in the system OnUpdate() function.
We do not use an IJobFor here because it is a bit more involved than a Job.WithCode(), and we are just starting to warm up to ECS! If you are feeling adventurous, you can implement AsteroidJobSystem with an IJobFor and checkout the performance difference between single jobs and parallel jobs for spawning asteroids. If you have interesting results, please reach out to the Moetsi team either on our Discord or emailing us at [email protected] and we will include it in this gitbook!
    Hit the "play" button and see the spawned asteroids
      Not working? Reimport the ConvertedSubScene if there are issues grabbing the AsteroidAuthoringComponent Singleton
    Checkout the Entity Debugger to see all the newly created entities
Update AsteroidSpawnSystem and check out the Asteroids in Entity Debugger
    But where are all our asteroids?
    We can see from the Entity Debugger that the Entities are there but the Translation values of the asteroids are verrrrrry far apart, so we cannot see them and
    we need to update our GameSettings
    Let's go to our ConvertedSubScene in the Hierarchy, select Game Settings, and change our levelWidth, levelHeight, and levelDepth to 20 and change the number of asteroids to 20,000 in the Inspector
Instantiating 20,000 Asteroid prefab entities
    Woah. That is way too many 😬
    Let's change the number of Asteroids to 200
Change the number of asteroids from 20,000 to 200
    This looks more like an asteroid field ☑️
We now have spawned an Asteroid prefab (entity prefab) using ECS
    we created an Asteroid prefab
    we created a AsteroidTag authoring component
    We placed the AsteroidTag on the prefab
    We declared our Asteroid using IDeclareReferencedPrefabs interface with PrefabReference
    We created AsteroidSpawnSystem which spawns Asteroids in a cube of a given height, width and depth
Many people come to ECS looking to increase the performance of their games and tools. While what you've built so far is a toy example, take a second to play around with the asteroid counts.
Push the asteroid counts as high as your machine can handle. Get comfortable with exploring which Systems carry the most load.
You've taken your first steps in high concurrency ECS systems! Congratulations!

Adding movement to prefabs

    Create a Velocity component (right click Scripts and Prefabs > Create > ECS > Authoring Component Type, you get the hang of this now, right?)
    We will add this to our asteroid prefab and update the AsteroidSpawnSystem to set the Velocity component on our asteroid entity when instantiating
      Just a heads up: we're going to shake things up a bit in the next section, where we use Unity Physics for velocity instead
    paste the code snippet below into VelocityComponent.cs:
1
using Unity.Entities;
2
using Unity.Mathematics;
3
4
[GenerateAuthoringComponent]
5
public struct VelocityComponent : IComponentData
6
{
7
public float3 Value;
8
}
Copied!
    Then add the VelocityComponent to the asteroid prefab
      We can add the VelocityComponent to the prefab because it has the [GenerateAuthoringComponent] decorator
Creating the VelocityComponent
    We must now update our AsteroidSpawnSystem to initialize the asteroids VelocityComponent
    Add the below code snippet to the end of the Job.WithCode() code block (just before the closing curly brace) in AsteroidSpawnSystem.cs:
1
2
//We will now set the VelocityComponent of our asteroids
3
//here we generate a random Vector3 with x, y and z between -1 and 1
4
var randomVel = new Vector3(rand.NextFloat(-1f, 1f), rand.NextFloat(-1f, 1f), rand.NextFloat(-1f, 1f));
5
//next we normalize it so it has a magnitude of 1
6
randomVel.Normalize();
7
//now we set the magnitude equal to the game settings
8
randomVel = randomVel * settings.asteroidVelocity;
9
//here we create a new VelocityComponent with the velocity data
10
var vel = new VelocityComponent{Value = new float3(randomVel.x, randomVel.y, randomVel.z)};
11
//now we set the velocity component in our asteroid prefab
12
commandBuffer.SetComponent(e, vel);
Copied!
    Reimport ConvertedSubScene, hit "play", then go checkout the Entity list of asteroids in the Entity Debugger, now with their new VelocityComponents
Set the VelocityComponent on the asteroid entities and check out the results in Entity Debugger
    Now we need to make a system to update Translation values based on VelocityComponent data
    Create MovementSystem in Scripts and Prefabs (Create > ECS > System)
    Create MovementSystem and paste this code snippet into MovementSystem.cs:
1
using Unity.Entities;
2
using Unity.Mathematics;
3
using Unity.Transforms;
4
using UnityEngine;
5
using Unity.Burst;
6
7
public class MovementSystem : SystemBase
8
{
9
10
protected override void OnUpdate()
11
{
12
var deltaTime = Time.DeltaTime;
13
Entities
14
.ForEach((ref Translation position, in VelocityComponent velocity) =>
15
{
16
position.Value.xyz += velocity.Value * deltaTime;
17
}).ScheduleParallel();
18
}
19
}
Copied!
    Notice that this system is much simpler than AsteroidSpawningSystem
    This is because there are no structural changes to be made and we do not need to use any data beyond the data that is already on the entities we want to update
      All we want to do in MovementSystem is read data from one component (Velocity), and use it to adjust the data in another component (Translation)
Use the Entities.ForEach construction provided by the SystemBase class as a concise way to define and execute your algorithms over entities and their components. Entities.ForEach executes a lambda function you define over all the entities selected by an entity query.
To execute a job lambda function, you either schedule the job using Schedule() and ScheduleParallel(), or execute it immediately (on the main thread) with Run(). You can use additional methods defined on Entities.ForEach to set the entity query as well as various job options.
...
When you define the lambda function to use with Entities.ForEach, you can declare parameters that the SystemBase class uses to pass in information about the current entity when it executes the function.
A typical lambda function looks like:
1
2
Entities.ForEach(
3
(Entity entity,
4
int entityInQueryIndex,
5
ref Translation translation,
6
in Movement move) => { /* .. */})
Copied!
By default, you can pass up to eight parameters to an Entities.ForEach lambda function. (If you need to pass more parameters, you can define a custom delegate). When using the standard delegates, you must group the parameters in the following order:
1
1. Parameters passed-by-value first (no parameter modifiers)
2
2. Writable parameters second (`ref` parameter modifier)
3
3. Read-only parameters last (`in` parameter modifier)
Copied!
All components should use either the ref or the in parameter modifier keywords. Otherwise, the component struct passed to your function is a copy instead of a reference. This means an extra memory copy for read-only parameters and means that any changes to components you intended to update are silently thrown when the copied struct goes out of scope after the function returns.
    Our MovementSystem queries all entities that have both a Translation and a Velocity component
      We put "ref Translation position" because we will be writing to the Translation component
      We put "in VelocityComponent velocity" because we are only reading from the Velocity component
    Wait, where is this "Burst" we have been hearing so much about?
      Job.WithCode() and Entities.ForEach() are automatically Burst compiled (woo!)
      Sometimes you must specify .WithoutBurst() for certain workflows (we will see this later)
      Burst documentation if you want to read up.
You can execute the lambda function on the main thread using Run(), as a single job using Schedule(), or as a parallel job using ScheduleParallel(). These different execution methods have different constraints on how you access data. In addition, Burst uses a restricted subset of the C# language, so you need to specify WithoutBurst() when using C# features outside this subset (including accessing managed types).
    If possible use .ScheduleParallel() to make the most of Unity ECS
      That is the whole point of doing things in this new data-oriented way, performance improvements! 🚀
      If you're curious, try and create the same AsteroidSpawnSystem using MonoBehaviours to see the performance difference
      Sometimes you will not be able to use Burst, Schedule(), or ScheduleParallel() based on your workflow (limitations of the technology)
The following table shows which features are currently supported in Entities.ForEach for the different methods of scheduling available in SystemBase:
Supported Feature
Run
Schedule
ScheduleParallel
Capture local value type
x
x
x
Capture local reference type
x (only WithoutBurst)
Writing to captured variables
x
Use field on the system class
x (only WithoutBurst)
Methods on reference types
x (only WithoutBurst)
Shared Components
x (only WithoutBurst)
Managed Components
x (only WithoutBurst)
Structural changes
x (only WithoutBurst and WithStructuralChanges)
SystemBase.GetComponent
x
x
x
SystemBase.SetComponent
x
x
GetComponentDataFromEntity
x
x
x (only as ReadOnly)
HasComponent
x
x
x
WithDisposeOnCompletion
x
x
x
An Entities.ForEach construction uses specialized intermediate language (IL) compilation post-processing to translate the code you write for the construction into correct ECS code. This translation allows you to express the intent of your algorithm without having to include complex, boilerplate code. However, it can mean that some common ways of writing code are not allowed.
    Hit "play" and see the asteroids move in their random velocities
    Go to the GameSettings in ConvertedSubScene and adjust the Asteroid Velocity to 2 to slow down the asteroids and make them more Space-like
    Reimport the Sub Scene, hit "play", and checkout the Entity Debugger to see how the asteroids' Translation values are changed in real-time
With MovementSystem our Asteroids now move based on their VelocityComponent values
We now have asteroids prefabs moving in random directions
    We created a VelocityComponent and added it to the asteroid prefab
    We added to the AsteroidSpawnSystem to set random velocities on the asteroids
    We created MovementSystem to take any entity with a Translation and VelocityComponent and adjust the Translation value using the VelocityComponent

Destroying prefabs

    We are going to destroy any asteroid prefabs that leave the perimeter of our cube
    We will create an AsteroidsOutOfBoundsSystem that adds a DestroyTag to any asteroid that leaves the cube perimeter
    Create DestroyTag and paste this code snippet into DestroyTag.cs:
1
using Unity.Entities;
2
3
public struct DestroyTag : IComponentData
4
{
5
}
6
Copied!
Creating the DestroyTag
    Notice we did not add [GenerateAuthoringComponent] to this tag
      This is because this is data we add at runtime not at authoring time
    Now let's create the AsteroidsOutOfBoundsSystem that will add the DestroyTag to any asteroid that leaves the cube's perimeter
    Create AsteroidsOutOfBoundsSystem and paste this code snippet into AsteroidsOutOfBoundsSystem.cs:
1
using Unity.Burst;
2
using Unity.Entities;
3
using Unity.Collections;
4
using Unity.Mathematics;
5
using Unity.Jobs;
6
using Unity.Transforms;
7
using UnityEngine;
8
//We are adding this system within the FixedStepSimulationGroup
9
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
10
[UpdateBefore(typeof(EndFixedStepSimulationEntityCommandBufferSystem))]
11
public class AsteroidsOutOfBoundsSystem : SystemBase
12
{
13
//We are going to use the EndFixedStepSimECB
14
//This is because when we use Unity Physics our physics will run in the FixedStepSimulationSystem
15
//We are dipping our toes into placing our systems in specific system groups
16
//The FixedStepSimGroup has its own EntityCommandBufferSystem we will use to make the structural change
17
//of adding the DestroyTag
18
private EndFixedStepSimulationEntityCommandBufferSystem m_EndFixedStepSimECB;
19
protected override void OnCreate()
20
{
21
//We grab the EndFixedStepSimECB for our OnUpdate
22
m_EndFixedStepSimECB = World.GetOrCreateSystem<EndFixedStepSimulationEntityCommandBufferSystem>();
23
//We want to make sure we don't update until we have our GameSettingsComponent
24
//because we need the data from this component to know where the perimeter of our cube is
25
RequireSingletonForUpdate<GameSettingsComponent>();
26
}
27
28
protected override void OnUpdate()
29
{
30
//We want to run this as parallel jobs so we need to add "AsParallelWriter" when creating
31
//our command buffer
32
var commandBuffer = m_EndFixedStepSimECB.CreateCommandBuffer().AsParallelWriter();
33
//We must declare our local variables that we will use in our job
34
var settings = GetSingleton<GameSettingsComponent>();
35
//This time we query entities with components by using "WithAll" tag
36
//This makes sure that we only grab entities with an AsteroidTag component so we don't affect other entities
37
//that might have passed the perimeter of the cube
38
Entities
39
.WithAll<AsteroidTag>()
40
.ForEach((Entity entity, int nativeThreadIndex, in Translation position) =>
41
{
42
//We check if the current Translation value is out of bounds
43
if (Mathf.Abs(position.Value.x) > settings.levelWidth/2 ||
44
Mathf.Abs(position.Value.y) > settings.levelHeight/2 ||
45
Mathf.Abs(position.Value.z) > settings.levelDepth/2)
46
{
47
//If it is out of bounds wee add the DestroyTag component to the entity and return
48
commandBuffer.AddComponent(nativeThreadIndex, entity, new DestroyTag());
49
return;
50
}
51
}).ScheduleParallel();
52
//We add the dependencies to the CommandBuffer that will be playing back these structural changes (adding a DestroyTag)
53
m_EndFixedStepSimECB.AddJobHandleForProducer(Dependency);
54
}
55
}
Copied!
    Notice we are placing this system in a specific SystemGroup, the FixedStepSimulationGroup
      We want to make sure it updates before the EndFixedStepSimulationEntityCommandBufferSystem because the latter is where recorded structural changes will playback
      We want to start getting comfortable with placing systems in different groups and utilizing different EntityCommandBufferSystems because not only is it important in ECS in general, but also because we will be using Unity Physics in the next section (which is run in the FixedStepSimulationGroup)
    We use .WithAll<AsteroidTag>() to query all entities with an AsteroidTag
    We also needed to include "int nativeThreadIndex" in our .ForEach() because when running parallel jobs, we must include the nativeThreadIndex when making our changes. It is needed for parallel jobs to work!
      commandBuffer.AddComponent(nativeThreadIndex, entity, new DestroyTag());
    Now we need to create a system that destroys any asteroids with a DestroyTag
    Create AsteroidsDestructionSystem and paste this code snippet into AsteroidsDestructionSystem.cs:
1
using Unity.Burst;
2
using Unity.Entities;
3
using Unity.Collections;
4
using Unity.Mathematics;
5
using Unity.Jobs;
6
using Unity.Transforms;
7
using UnityEngine;
8
9
//We are going to update LATE once all other systems are complete
10
//because we don't want to destroy the Entity before other systems have
11
//had a chance to interact with it if they need to
12
[UpdateInGroup(typeof(LateSimulationSystemGroup))]
13
public class AsteroidsDestructionSystem : SystemBase
14
{
15
private EndSimulationEntityCommandBufferSystem m_EndSimEcb;
16
17
protected override void OnCreate()
18
{
19
//We grab the EndSimulationEntityCommandBufferSystem to record our structural changes
20
m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
21
}
22
23
protected override void OnUpdate()
24
{
25
//We add "AsParallelWriter" when we create our command buffer because we want
26
//to run our jobs in parallel
27
var commandBuffer = m_EndSimEcb.CreateCommandBuffer().AsParallelWriter();
28
29
//We now any entities with a DestroyTag and an AsteroidTag
30
//We could just query for a DestroyTag, but we might want to run different processes
31
//if different entities are destroyed, so we made this one specifically for Asteroids
32
Entities
33
.WithAll<DestroyTag, AsteroidTag>()
34
.ForEach((Entity entity, int nativeThreadIndex) =>
35
{
36
commandBuffer.DestroyEntity(nativeThreadIndex, entity);
37
38
}).ScheduleParallel();
39
40
//We then add the dependencies of these jobs to the EndSimulationEntityCOmmandBufferSystem
41
//that will be playing back the structural changes recorded in this sytem
42
m_EndSimEcb.AddJobHandleForProducer(Dependency);
43
44
}
45
}
Copied!
    We run this system in the LateSimulationSystemGroup
      This way all systems are able to interact with the entity before we delete it
    We will use the LateSimulationSystemGroup's CommandBufferSystem to playback our recorded structural changes (destroying the asteroid entity)
    Hit "play" and see how the asteroids are destroyed when they pass the perimeter and how the AsteroidSpawnSystem creates new asteroids
    Check out the EntityDebugger and see the systems in their system grou

Celebrate your win; Congrats!

You just made a giant cloud of asteroids! Congratulations, you've taken your first steps in high concurrency ECS systems!
Many come to ECS looking to increase the performance of their games and tools. While what you've built so far is a toy example, take a second to play around with the asteroid counts. Push the asteroid counts as high as your machine can handle. Get comfortable with exploring which Systems carry the most load.
We now have an asteroid field
    We created a DestroyTag which is added by the AsteroidsOutOfBoundsSystem when an asteroid leaves the cube perimeter
    We created AsteroidsDestructionSystem that destroys asteroids with a destroy tag
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Spawning-Updating-and-Destroying-Asteroids'
Last modified 6mo ago