Spawn and Move Prefabs
Full workflows and code to programmatically spawn, update, and destroy prefab entities
What you'll develop on this page

Use game settings to programmatically spawn asteroid prefabs to create an asteroid field (cube).
Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Spawning-Updating-and-Destroying-Asteroids
Spawning asteroid prefabs
First, some background
An entity prefab is nothing more than an entity with a
Prefab
tag and aLinkedEntityGroup
. 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.
// Authoring component public class PrefabReference : MonoBehaviour { public GameObject Prefab; } // Runtime component public struct PrefabEntityReference : IComponentData { public Entity Prefab; }
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.From ECS Conversion Workflow documentation "Prefabs" section
Setting up a prefab with ECS
Right click in the 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
Although this isn't necessary, it tends to help you remember how components are meant to be used
AsteroidTag.cs
using Unity.Entities;
public struct AsteroidTag : IComponentData
{
}

Try to put the AsteroidTag on the asteroid prefab

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
using Unity.Entities;
[GenerateAuthoringComponent]
public struct AsteroidTag : IComponentData
{
}
Now try to put the AsteroidTag on the Asteroid prefab again (select Asteroid in Hierarchy > Click "Add Component" in the Inspector)

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)
using Unity.Entities;
[GenerateAuthoringComponent]
public struct AsteroidAuthoringComponent : IComponentData
{
public Entity Prefab;
}
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

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)
The latest update creates a "bad" system (Unity has not updated the auto-generated systems yet) that is missing the "partial" keyword
In AsteroidSpawnSystem change
public class AsteroidSpawnSystem : SystemBase
topublic partial class AsteroidSpawSystem : SystemBase
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.
A system's update loop is driven by its parent ComponentSystemGroup. A ComponentSystemGroup is, itself, a specialized kind of system that is responsible for updating its child systems. Groups can be nested. Systems derive their time data from the World they are running in; time is updated by the UpdateWorldTimeSystem.
Hit the "play" button and check out the Systems window in our DOTS Windows
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
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
public partial class AsteroidSpawnSystem : SystemBase
{
protected override void OnUpdate()
{
// Assign values to local variables captured in your job here, so that it has
// everything it needs to do its work when it runs later.
// For example,
// float deltaTime = Time.DeltaTime;
// This declares a new kind of job, which is a unit of work to do.
// The job is declared as an Entities.ForEach with the target components as parameters,
// meaning it will process all entities in the world that have both
// Translation and Rotation components. Change it to process the component
// types you want.
Entities.ForEach((ref Translation translation, in Rotation rotation) => {
// Implement the work to perform for each entity here.
// You should only access data that is local or that is a
// field on this job. Note that the 'rotation' parameter is
// marked as 'in', which means it cannot be modified,
// but allows this job to run in parallel with other jobs
// that want to read Rotation component data.
// For example,
// translation.Value += math.mul(rotation.Value, new float3(0, 0, 1)) * deltaTime;
}).Schedule();
}
}
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.
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.
To smoothly change the archetype of an entity, you can add or remove components at runtime. For example, if you remove the
Renderer
component from entity B, it then moves to archetype N.From Core ECS Overview
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.
Structural changes not only require a sync point, but they also invalidate all direct references to any component data. This includes instances of DynamicBuffer and the result of methods that provide direct access to the components such as ComponentSystemBase.GetComponentDataFromEntity.
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 you cannot use an EntityCommandBufferSystem for a task, try to group any systems that make structural changes together in the system execution order. Two systems that both make structural changes only incur one sync point if they update sequentially.
From ECS Sync points
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 andWithDisposeOnCompletion
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:
using System.Diagnostics;
using Unity.Entities;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.Burst;
public partial class AsteroidSpawnSystem : SystemBase
{
//This will be our query for Asteroids
private EntityQuery m_AsteroidQuery;
//We will use the BeginSimulationEntityCommandBufferSystem for our structural changes
private BeginSimulationEntityCommandBufferSystem m_BeginSimECB;
//This will be our query to find GameSettingsComponent data to know how many and where to spawn Asteroids
private EntityQuery m_GameSettingsQuery;
//This will save our Asteroid prefab to be used to spawn Asteroids
private Entity m_Prefab;
protected override void OnCreate()
{
//This is an EntityQuery for our Asteroids, they must have an AsteroidTag
m_AsteroidQuery = GetEntityQuery(ComponentType.ReadWrite<AsteroidTag>());
//This will grab the BeginSimulationEntityCommandBuffer system to be used in OnUpdate
m_BeginSimECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
//This is an EntityQuery for the GameSettingsComponent which will drive how many Asteroids we spawn
m_GameSettingsQuery = GetEntityQuery(ComponentType.ReadWrite<GameSettingsComponent>());
//This says "do not go to the OnUpdate method until an entity exists that meets this query"
//We are using GameObjectConversion to create our GameSettingsComponent so we need to make sure
//The conversion process is complete before continuing
RequireForUpdate(m_GameSettingsQuery);
}
protected override void OnUpdate()
{
//Here we set the prefab we will use
if (m_Prefab == Entity.Null)
{
//We grab the converted PrefabCollection Entity's AsteroidAuthoringComponent
//and set m_Prefab to its Prefab value
m_Prefab = GetSingleton<AsteroidAuthoringComponent>().Prefab;
//we must "return" after setting this prefab because if we were to continue into the Job
//we would run into errors because the variable was JUST set (ECS funny business)
//comment out return and see the error
return;
}
//Because of how ECS works we must declare local variables that will be used within the job
//You cannot "GetSingleton<GameSettingsComponent>()" from within the job, must be declared outside
var settings = GetSingleton<GameSettingsComponent>();
//Here we create our commandBuffer where we will "record" our structural changes (creating an Asteroid)
var commandBuffer = m_BeginSimECB.CreateCommandBuffer();
//This provides the current amount of Asteroids in the EntityQuery
var count = m_AsteroidQuery.CalculateEntityCountWithoutFiltering();
//We must declare our prefab as a local variable (ECS funny business)
var asteroidPrefab = m_Prefab;
//We will use this to generate random positions
var rand = new Unity.Mathematics.Random((uint)Stopwatch.GetTimestamp());
Job
.WithCode(() => {
for (int i = count; i < settings.numAsteroids; ++i)
{
// this is how much within perimeter asteroids start
var padding = 0.1f;
// we are going to have the asteroids start on the perimeter of the level
// choose the x, y, z coordinate of perimeter
// so the x value must be from negative levelWidth/2 to positive levelWidth/2 (within padding)
var xPosition = rand.NextFloat(-1f*((settings.levelWidth)/2-padding), (settings.levelWidth)/2-padding);
// so the y value must be from negative levelHeight/2 to positive levelHeight/2 (within padding)
var yPosition = rand.NextFloat(-1f*((settings.levelHeight)/2-padding), (settings.levelHeight)/2-padding);
// so the z value must be from negative levelDepth/2 to positive levelDepth/2 (within padding)
var zPosition = rand.NextFloat(-1f*((settings.levelDepth)/2-padding), (settings.levelDepth)/2-padding);
//We now have xPosition, yPostiion, zPosition in the necessary range
//With "chooseFace" we will decide which face of the cube the Asteroid will spawn on
var chooseFace = rand.NextFloat(0,6);
//Based on what face was chosen, we x, y or z to a perimeter value
//(not important to learn ECS, just a way to make an interesting prespawned shape)
if (chooseFace < 1) {xPosition = -1*((settings.levelWidth)/2-padding);}
else if (chooseFace < 2) {xPosition = (settings.levelWidth)/2-padding;}
else if (chooseFace < 3) {yPosition = -1*((settings.levelHeight)/2-padding);}
else if (chooseFace < 4) {yPosition = (settings.levelHeight)/2-padding;}
else if (chooseFace < 5) {zPosition = -1*((settings.levelDepth)/2-padding);}
else if (chooseFace < 6) {zPosition = (settings.levelDepth)/2-padding;}
//we then create a new translation component with the randomly generated x, y, and z values
var pos = new Translation{Value = new float3(xPosition, yPosition, zPosition)};
//on our command buffer we record creating an entity from our Asteroid prefab
var e = commandBuffer.Instantiate(asteroidPrefab);
//we then set the Translation component of the Asteroid prefab equal to our new translation component
commandBuffer.SetComponent(e, pos);
}
}).Schedule();
//This will add our dependency to be played back on the BeginSimulationEntityCommandBuffer
m_BeginSimECB.AddJobHandleForProducer(Dependency);
}
}
Read through the comments to get a line-by-line understanding of what is going on
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
Hit the "play" button and see the spawned asteroids
Not working? Reimport the ConvertedSubScene if there are issues grabbing the AsteroidAuthoringComponent Singleton
Checkout the DOTS Hierarchy to see all the newly created entities

But where are all our asteroids?
We can see from the DOTS Hierarchy that the Entities are there but the Translation values of the asteroids are verrrrrry far apart, so we cannot see them and so 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
But we still can't see the asteroids even now when they are closer together, WHAT GIVES?!
The issue is that we are still using the "built in" render pipeline, rather than the Universal Render Pipeline (URP). Unity does not automatically change over. Following Unity's docs let's create a Universal Render Pipeline Asset and then Add the Asset to your Graphics Settings
- Creating the Universal Render Pipeline Asset
The Universal Render Pipeline Asset controls the global rendering and quality settings of your Project, and creates the rendering pipeline instance. The rendering pipeline instance contains intermediate resources and the render pipeline implementation.
To create a Universal Render Pipeline Asset:
In the Editor, go to the Project window.
Right-click in the Project window in Assets/, and select Create > Rendering > Universal Render Pipeline > Pipeline Asset. Alternatively, navigate to the menu bar at the top, and select Assets > Create > Rendering > Universal Render Pipeline > Pipeline Asset (Forward Renderer).
You can either leave the default name for the new Universal Render Pipeline Asset, or type a new one.
Adding the Asset to your Graphics settings
To use URP, you need to add the newly created Universal Render Pipeline Asset to your Graphics settings in Unity. If you don't, Unity still tries to use the Built-in render pipeline.
To add the Universal Render Pipeline Asset to your Graphics settings:
Navigate to Edit > Project Settings... > Graphics.
In the Scriptable Render Pipeline Settings field, add the Universal Render Pipeline Asset you created earlier. When you add the Universal Render Pipeline Asset, the available Graphics settings immediately change. Your Project is now using URP.
Now let's hit play and see those Asteroids!
Why can't we see them?! It is because we are still using a Material from the built in render pipeline. Let's make a material that is compatible with Universal Render Pipeline.
Right click in Assets/ and Prefabs and choose Material and call it GreyMaterial
Choose the base map to be a Grey Color

Now let's add it to our AsteroidPrefab

Now let's hit play

Woah. That is way too many 😬
Let's change the number of Asteroids 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 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:
using Unity.Entities;
using Unity.Mathematics;
[GenerateAuthoringComponent]
public struct VelocityComponent : IComponentData
{
public float3 Value;
}
Then add the VelocityComponent to the asteroid prefab
We can add the VelocityComponent to the prefab because it has the [GenerateAuthoringComponent] decorator

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:
//We will now set the VelocityComponent of our asteroids
//here we generate a random Vector3 with x, y and z between -1 and 1
var randomVel = new Vector3(rand.NextFloat(-1f, 1f), rand.NextFloat(-1f, 1f), rand.NextFloat(-1f, 1f));
//next we normalize it so it has a magnitude of 1
randomVel.Normalize();
//now we set the magnitude equal to the game settings
randomVel = randomVel * settings.asteroidVelocity;
//here we create a new VelocityComponent with the velocity data
var vel = new VelocityComponent{Value = new float3(randomVel.x, randomVel.y, randomVel.z)};
//now we set the velocity component in our asteroid prefab
commandBuffer.SetComponent(e, vel);
Reimport ConvertedSubScene, hit "play", then go checkout the Entity list of asteroids in the DOTS Hierarchy, now with their new VelocityComponents

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:
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using Unity.Burst;
public partial class MovementSystem : SystemBase
{
protected override void OnUpdate()
{
var deltaTime = Time.DeltaTime;
Entities
.ForEach((ref Translation position, in VelocityComponent velocity) =>
{
position.Value.xyz += velocity.Value * deltaTime;
}).ScheduleParallel();
}
}
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()
andScheduleParallel()
, or execute it immediately (on the main thread) withRun()
. 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:
Entities.ForEach( (Entity entity, int entityInQueryIndex, ref Translation translation, in Movement move) => { /* .. */})
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:
Parameters passed-by-value first (no parameter modifiers)
Writable parameters second (
ref
parameter modifier)Read-only parameters last (
in
parameter modifier)All components should use either the
ref
or thein
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 usingSchedule()
, or as a parallel job usingScheduleParallel()
. 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 specifyWithoutBurst()
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:
Capture local value type
x
x
x
Capture local reference type
x (only WithoutBurst and not in ISystem)
Writing to captured variables
x
Use field on the system class
x (only WithoutBurst)
Methods on reference types
x (only WithoutBurst and not in ISystem)
Shared Components
x (only WithoutBurst and not in ISystem)
Managed Components
x (only WithoutBurst and not in ISystem)
Structural changes
x (only WithStructuralChanges and not in ISystem)
SystemBase.GetComponent
x
x
x
SystemBase.SetComponent
x
x
GetComponentDataFromEntity
x
x
x (only as ReadOnly)
HasComponent
x
x
x
WithDisposeOnCompletion
x
x
x
WithScheduleGranularity
x
Note:
WithStructuralChanges()
will disable Burst. Do not use this option if you want to achieve high levels of performance in your Entities.ForEach (instead use an EntityCommandBuffer).An Entities.ForEach construction uses Roslyn source generators 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.
The following features are not currently supported:
Dynamic code in .With invocations
SharedComponent parameters by ref
Nested Entities.ForEach lambda expressions
Calling with delegate stored in variable, field or by method
SetComponent with lambda parameter type
GetComponent with writable lambda parameter
Generic parameters in lambdas
In systems with generic parameters
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

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:
using Unity.Entities;
public struct DestroyTag : IComponentData
{
}

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:
using Unity.Burst;
using Unity.Entities;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
//We are adding this system within the FixedStepSimulationGroup
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateBefore(typeof(EndFixedStepSimulationEntityCommandBufferSystem))]
public partial class AsteroidsOutOfBoundsSystem : SystemBase
{
//We are going to use the EndFixedStepSimECB
//This is because when we use Unity Physics our physics will run in the FixedStepSimulationSystem
//We are dipping our toes into placing our systems in specific system groups
//The FixedStepSimGroup has its own EntityCommandBufferSystem we will use to make the structural change
//of adding the DestroyTag
private EndFixedStepSimulationEntityCommandBufferSystem m_EndFixedStepSimECB;
protected override void OnCreate()
{
//We grab the EndFixedStepSimECB for our OnUpdate
m_EndFixedStepSimECB = World.GetOrCreateSystem<EndFixedStepSimulationEntityCommandBufferSystem>();
//We want to make sure we don't update until we have our GameSettingsComponent
//because we need the data from this component to know where the perimeter of our cube is
RequireSingletonForUpdate<GameSettingsComponent>();
}
protected override void OnUpdate()
{
//We want to run this as parallel jobs so we need to add "AsParallelWriter" when creating
//our command buffer
var commandBuffer = m_EndFixedStepSimECB.CreateCommandBuffer().AsParallelWriter();
//We must declare our local variables that we will use in our job
var settings = GetSingleton<GameSettingsComponent>();
//This time we query entities with components by using "WithAll" tag
//This makes sure that we only grab entities with an AsteroidTag component so we don't affect other entities
//that might have passed the perimeter of the cube
Entities
.WithAll<AsteroidTag>()
.ForEach((Entity entity, int entityInQueryIndex, in Translation position) =>
{
//We check if the current Translation value is out of bounds
if (Mathf.Abs(position.Value.x) > settings.levelWidth/2 ||
Mathf.Abs(position.Value.y) > settings.levelHeight/2 ||
Mathf.Abs(position.Value.z) > settings.levelDepth/2)
{
//If it is out of bounds wee add the DestroyTag component to the entity and return
commandBuffer.AddComponent(entityInQueryIndex, entity, new DestroyTag());
return;
}
}).ScheduleParallel();
//We add the dependencies to the CommandBuffer that will be playing back these structural changes (adding a DestroyTag)
m_EndFixedStepSimECB.AddJobHandleForProducer(Dependency);
}
}
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 entityInQueryIndex" in our .ForEach() because when running parallel jobs, we must include the entityInQueryIndexwhen making our changes. It is needed for parallel jobs to work!
commandBuffer.AddComponent(entityInQueryIndex, entity, new DestroyTag());
int entityInQueryIndex
— the index of the entity in the list of all entities selected by the query. Use the entity index value when you have a native array that you need to fill with a unique value for each entity. You can use the entityInQueryIndex as the index in that array. The entityInQueryIndex should also be used as thesortKey
for adding commands to a concurrent EntityCommandBuffer.
Now we need to create a system that destroys any asteroids with a DestroyTag
Create AsteroidsDestructionSystem and paste this code snippet into AsteroidsDestructionSystem.cs:
using Unity.Burst;
using Unity.Entities;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
//We are going to update LATE once all other systems are complete
//because we don't want to destroy the Entity before other systems have
//had a chance to interact with it if they need to
[UpdateInGroup(typeof(LateSimulationSystemGroup))]
public partial class AsteroidsDestructionSystem : SystemBase
{
private EndSimulationEntityCommandBufferSystem m_EndSimEcb;
protected override void OnCreate()
{
//We grab the EndSimulationEntityCommandBufferSystem to record our structural changes
m_EndSimEcb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
//We add "AsParallelWriter" when we create our command buffer because we want
//to run our jobs in parallel
var commandBuffer = m_EndSimEcb.CreateCommandBuffer().AsParallelWriter();
//We now any entities with a DestroyTag and an AsteroidTag
//We could just query for a DestroyTag, but we might want to run different processes
//if different entities are destroyed, so we made this one specifically for Asteroids
Entities
.WithAll<DestroyTag, AsteroidTag>()
.ForEach((Entity entity, int entityInQueryIndex) =>
{
commandBuffer.DestroyEntity(entityInQueryIndex, entity);
}).ScheduleParallel();
//We then add the dependencies of these jobs to the EndSimulationEntityCOmmandBufferSystem
//that will be playing back the structural changes recorded in this sytem
m_EndSimEcb.AddJobHandleForProducer(Dependency);
}
}
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 DOTS Hierarchy and see the AsteroidPrefabs get created and destroyed
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
Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Spawning-Updating-and-Destroying-Asteroids
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/
git checkout 'Spawning-Updating-and-Destroying-Asteroids'
Last updated