Game Object Conversion and SubScene
GameObject based data are not ECS compatible. It would be great if it is easy to author the data like before but instead getting ECS data at runtime so that we could work on them efficiently.
Games are just about player's input changing data and then see things rendered differently, therefore have fun. Those data are designed on the scene with Unity's WYSIWYG tools. But those GameObject
based data are not ECS compatible. It would be great if it is easy to author the data like before but instead getting ECS data at runtime so that we could work on them efficiently.
*This article is based on Entities package version 0.5.1
How the game was made before

Let's think why are we seeing the cubes drawn correctly each frame like this? In classical GameObject
, the engine code will compile the entire hierarchy tree and sort and call drawing methods ("draw call") in correct order. Between each call also it need to setup material and mesh to draw ("set pass call") and not set if it could use the same thing as the previous draw. Moreover Unity may attempt to combine meshes to reduce draw call (dynamic batching and static batching) which trade some CPU cycles each frame in the case of dynamic. If static it would be pre-combined at build time ready to draw.
In other words, we use Unity to author the game saved in YAML Scene
file. Though the game is not run on YAML, after loading a Scene
and following to the Cube .prefab
file and also load it, the engine would prepare some C++ memory of Transform
so the drawing code could draw according to them. (Where and what to draw)
This is one bulk of the performance each frame. It depends on data, the Transform
on each cube and the MeshFilter
which tell us what to draw. Unfortunately this sorting, caring about what order of draw call would get correct result, all of them are some form of iteration over data. (At native C++, but C++ doesn't equal fast if they are not arranged well.) And it is not so fast as these data are not lined up in the most optimal shape. You know ECS is good at iteration. And Burst plus ECS helps get the data threadable. So that's why we would like to make a game in ECS instead.
The other bulk is logic. Before getting to the draw, the cube may have Bouncing
component that keep changing its Transform
, etc. so to make your game exciting. Still, ECS would help this a lot to change data that leads to the draw.
The game is basically just this. You have some data that you change often and may change according to what the player input to the game, and it affect how the frame is drawn.
Both combined, believe it or not the players may already having fun! So in fact everyone is just changing data and see the drawings update and could relax to it. It is important to view the game from data standpoint or else you cannot accept how would one make a game in DOTS because it will feel really weird.
How to make a game in DOTS
If the data is in ECS database instead of GameObject
, imagine not only the logic to change data that lead to the draw is fast, also the draw itself would be fast because we could iterate faster, we could sort in jobs and use SIMD instructions.
Let's skip to the end for now. I already have used ECS and now I have 3 transformation matrices I want to draw (In IComponentData
, so iteration through it is linear and fast.) plus a Mesh
and Material
. I would like to see these 3 things now.
We can draw things without any inefficient GameObject
data container! See Unity's Graphics
API (https://docs.unity3d.com/ScriptReference/Graphics.html) and also this quite new BatchRendererGroup
(https://docs.unity3d.com/2019.3/Documentation/ScriptReference/Rendering.BatchRendererGroup.html) which supports NativeArray
. You can now draw from data. Just get data with ECS's EntityQuery
etc. and draw. You have made a game!
See this Japanese blog post https://virtualcast.jp/blog/2019/10/batchrenderergroup/ demonstrating the power of BatchRendererGroup
. Though the "batches" shown is massive and "saved by batching" is absolute zero, the performance is fantastic. Remember that batching is actually combining meshes, it takes work. Draw is just repeatedly calling methods given that nothing changes, these 1000+ "batches" number is not scary at all if you could left the NativeArray
memories in BatchRendererGroup
like that every frame. ("Set pass call" low number shows that there are not many material switches between draws.)
Hybrid Renderer
Unity made Hybrid Renderer Package (https://docs.unity3d.com/Packages/com.unity.rendering.hybrid@latest) which do that. If you have an Entity
with LocalToWorld
(this is the matrix), and that chunk has ISharedComponentData
of RenderMesh
( Mesh
and Material
and other stuff) associated, then it would use BatchRendererGroup
to draw things for you.
And entire chunk draw the same mesh and same material multiple times according to how many Entity
of LocalToWorld
you have got. This is already like mini-GPU instancing on CPU, it is just a draw call after draw call that is fast now and this dumb draw may even win over dynamic batching where you pay CPU to combine mesh differently each frame. It is 100% no need to perform set pass call in the same chunk, since a chunk shares the same Mesh
and Material
. And if your Material
has GPU instancing enabled, BatchRendererGroup
could really do it instead of rapidly drawing.
It's "hybrid" because BatchRendererGroup
is not an ECS API, it's a regular Unity API. But that doesn't mean it is not any less data-oriented. In fact how BatchRendererGroup
works is extremely data-oriented. A data of MaterialPropertyBlock
, a data of matrices. In NativeArray
even! So do not afraid to use it unless you know how to win with Graphics
API or use BatchRendererGroup
purely on your own. (Maybe to cut out unnecessary steps, like culling.)
Let's make a game in data!
Knowing that the game is all about changing data and see things, with just Hybrid Renderer package we could make a game. Let's say it is a game about seeing bouncing cube. And when you hold space bar it bounces crazier. It could even be kind of fun for kids.
First problem, how do I even get a hold of Mesh
and Material
in code. So I think I will use Resources.Load
to load a dummy prefab holding the asset's reference with this component :
using UnityEngine;
public class AssetHolder : MonoBehaviour
{
public Mesh myMesh;
public Material myMaterial;
}

With this system that has no work, just to create required data for Hybrid Renderer.
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class CubeGameSystem : JobComponentSystem
{
protected override void OnCreate()
{
base.OnCreate();
var myCube = EntityManager.CreateEntity(
ComponentType.ReadOnly<LocalToWorld>(),
ComponentType.ReadOnly<RenderMesh>()
);
EntityManager.SetComponentData(myCube, new LocalToWorld
{
Value = new float4x4(rotation: quaternion.identity, translation:new float3(1,2,3))
});
var ah = Resources.Load<GameObject>("AssetHolder").GetComponent<AssetHolder>();
EntityManager.SetSharedComponentData(myCube, new RenderMesh
{
mesh = ah.myMesh,
material = ah.myMaterial
});
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
return default;
}
}
And behold! My data-oriented cube at (1,2,3)
!

There is nothing in the hierarchy. It just appears.. and it is not pickable no matter how you click. It is just a drawings of a computer. The fact that you could pick things before is because of GameObject
data container linking each things you see to each thing you author.
If you look at Entity Debugger right now, you notice quite few more things Hybrid Renderer added. It would cull things you cannot see out before submitting to BatchRendererGroup
too.

By the way, this image reads : "You have 1 chunk, this chunk has a capacity of 128 entities (a chunk sized 16kB therefore 1 entity takes 125 bytes) and you have used 1/128 out of this one chunk. You can have 127 more cubes that is continuous in memory. There would be 128 LocalToWorld
right next to each other for Hybrid Renderer to take them into BatchRendererGroup
.
Transform systems
Now that you have a data and it became a drawing, let's make some gameplay.
I am going to make a gameplay by making it bounce but working with LocalToWorld
is a pain. Imagine having to modify the correct slot in the 4x4 matrix. So Unity made us something good. There are tons of systems waiting to be used.

With easier to use Translation
, Rotation
, Scale
/ NonUniformScale
, those systems would get them into matrix form LocalToWorld
for you. You can even use Parent
and say which Entity
with TRS LTW stuff is its parent, then the LocalToWorld
result you get will be based on parent. e.g. Transform
at zero, but has Parent
assigned, the resulting LocalToWorld
is not zero anymore, but exactly at parent's LTW.
I have added just Translation
to the cube. The gameplay system could now modify this instead of LocalToWorld
. Some system inside the Transform package would use it to make LTW. I added Cube
tag component so it feels more like a cube when I query and not just any Entity
.
var myCube = EntityManager.CreateEntity(
ComponentType.ReadOnly<Translation>(),
ComponentType.ReadOnly<Cube>(), //Tag
ComponentType.ReadOnly<LocalToWorld>(),
ComponentType.ReadOnly<RenderMesh>()
);
Now, the bouncing. I know cosine function would give a bouncing value from 0 to 1 and back to 0 if I give it an increasing value. So let's use Time.ElapsedTime
for this.
The beauty of ECS is that you can continue adding systems to the game "horizontally" to build up the gameplay. The pattern is to query data from central ECS great and fast database, do things, and store back. You may feel uneasy about this query and store back ceremony each time you add more systems, but it make things scalable. And Unity ECS lib make sure that repeated query are all fast so you aren't discouraged for slice and dicing logic into tons of systems.
This system, on main thread check if space bar is pressed then declare a variable and also time variable. Capture them into lambda jobs, which get all the Cube
with Translation
then change their Translation
on multithreaded jobs that each thread work on 1 chunk of Cube
and Translation
. Time
is a property maintained by ECS lib that take values from classic Unity Time
each frame.
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
public class CubeBouncingSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
int multiplier = Input.GetKey(KeyCode.Space) ? 100 : 20;
var elapsedTime = Time.ElapsedTime;
JobHandle jh = Entities.WithAll<Cube>().ForEach((ref Translation t) =>
{
float3 value = t.Value;
value.y = math.cos((float) elapsedTime * multiplier);
t.Value = value;
}).Schedule(inputDeps);
return jh;
}
}

Worker threads are used for this task. When you make games in systems, eventually almost everything would take turn using worker threads so they are not too idle.

This extreme of making a game is painful
You could see some problems. It is already kinda hard to map game problems to data-oriented. (Any other game not about bouncing cube) Now you cannot even use WYSIWYG tools to make a game anymore. Getting LTW + Mesh
+ Material
like this for an entire game will be a nightmare. To make a game all in data is scary though it would be very efficient. Imagine how could you author something that require artistic sense and iteration in only code editor. It's like back to Cocos2D. You want to touch things in editor to plan out.
But remember this cube's drawing is darn fast. If you have more cubes the draw code would serve them into BatchRendererGroup
together because they are already together in the same chunk with LocalToWorld
lined up memory wise. Duplicating a cube is no longer duplicating GameObject
which maybe even costly, it is just make more Entity
with LocalToWorld
to the same chunk with Mesh
and Material
already assigned.
Also an Entity
that keep updating back to GameObject
(the "proxy", or the GameObjectEntity
) is no good either. We want pure ECS at runtime, not ECS that must bridge back to MonoBehaviour
.
You would like ECS performance but you don't want to make a game like this.
The conversion workflow
So an approach Unity team is going is to use regular GameObject
just for authoring. At runtime, there is no GameObject
but only ECS data converted from those GameObject
.
The reason why I have to prep you about the renderer and transforms before, because all the conversion does is to get you the fast transform and rendering entity ready from GameObject
that is easy to author with.
Component object
Before going further, I assume you know about component objects. It is not just IComponentData
and friends you can attach to Entity
. You can even use MonoBehaviour
type.
It is just that you cannot use it in jobs and many API with <T>
has IComponentData
constraint so you cannot use them. But it is with the Entity
for sure and there are API that wants the type T
that is not IComponentData
constrained. Here are examples of API that supports component objects.
EntityManager
:AddComponentObject
,GetComponentObject
. If you just add from thin air and get it back, you would get anull
.ComponentType
: You can create a type withT
that isMonoBehaviour
.system.GetEntityQuery
,em.CreateArchetype
: Therefore, you can useComponentType
ofMonoBehaviour
to create a query that contains component object.EntityQuery
:ToComponentArray
which returnsT[]
. That returns managed array of managed components instead ofNativeArray<T>
ofToComponentDataArray
.
Conversion world, destination world, primary entity
The internal conversion pattern is by creating a special purpose World
(therefore you get a new EntityManager
, new database for entities) with some special conversion systems in it. This is the conversion world. The conversion world also know one more world, the destination world to store conversion result.
Because conversion world is an ECS World
, we would have to start out with some Entity
to perform work, and not really start from GameObject
in Hierarchy. The importing process from classic GameObject
scene is easy thanks to component objects I mentioned earlier. It is possible to get a bunch of Entity
that has component objects (class type components) replicating exactly the classical Hierarchy. That is, each entity has Transform
ECS component. Has RectTransform
ECS component. Has MeshFilter
ECS component. Has LineRenderer
ECS component. etc. And by having just the Transform
, you also retain complete hierarchy information.
These Entity
aren't good enough, we want a real entity with no component objects. I want Translation
+ LocalToWorld
and not a class type Transform
. Only optimized IComponentData
.
By updating the conversion world once, therefore updating all special systems in that world once, it would work on the simply imported component object entities and produce conversion results in the destination world. At least, it would create an empty Entity
for each Transform
component object found in conversion world.

The resulting Entity
A in the destination world is called the "primary entity of GameObject
A on the Hierarchy". The reason for this wording instead of something like "resulting entity" is because conversion is not necessarily one to one! It could be structured so that A in the image produce A1 and A2 in the destination world. In that case, one of them is the primary entity.
A special feature while working in the conversion world is that you can "get primary entity" from GameObject
. It could be also from any MonoBehaviour
component in which case it will just .gameObject
and use that instead. For example from Transform
of the A Entity
in the conversion world. It will be useful in more advanced conversion.
The conversion world along with inefficient Entity
with component objects are then destroyed.
GameObjectConversionSystem
The special systems for use inside the conversion world could be marked with :
[WorldSystemFilter(WorldSystemFilterFlags.GameObjectConversion)]
Then they would be ALL scanned and added during conversion. But this attribute is inheritable, and recommended approach is to just subclass from GameObjectConversionSystem
which also has that attribute.
For example TransformConversion
system. This is what will look for Transform
component object in the conversion world, then get you LocalToWorld
and Translate
/ Rotation
/ NonUniformScale
on the primary entity in the destination world. It even know not to add NonUniformScale
just yet in the result if the Transform
has 1,1,1
scale!

One more built-in system worth looking for is the MeshRendererConversion
. This is so MeshFilter
and MeshRenderer
got migrated into RenderMesh
with Mesh
and Material
from both original component.

Conversion order
As the conversion systems are all scanned and bunched up together, the order is not defined as much. Ordering maybe important, imagine you want to "get primary entity" while in conversion and expect to find the LocalToWorld
there, then you have to make sure your conversion system came after Unity's TransformConversion
.
It is possible with good old UpdateInGroup
attribute. But in conversion world, there are new set of groups.
public class GameObjectDeclareReferencedObjectsGroup : ComponentSystemGroup { }
public class GameObjectBeforeConversionGroup : ComponentSystemGroup { }
public class GameObjectConversionGroup : ComponentSystemGroup { }
public class GameObjectAfterConversionGroup : ComponentSystemGroup { }
public class GameObjectExportGroup : ComponentSystemGroup { }
If you don't say anything then it go to the middle one GameObjectConversionGroup
. So if you want to work on conversion on primary entity further from Unity's built-in conversion system, you have to make sure you came after. Note that you can't [UpdateAfter(typeof(TransformConversion))]
as that type is not public
. But in the screenshot you see that it says it go to before group. Therefore all your conversion system in the middle group can expect to see LocalToWorld
and friends waiting on the primary entity. MeshRendererConversion
on the other hand goes in the middle group. So if you want to work on RenderMesh
ISharedComponentData
produced, you must put your systems on the after group.
The declare and export group are special because it came before and after some specific procedures. While all 3 middle groups are just for organization purpose and really run one after another. Therefore, to justify a reason to put in other groups that aren't the middle 3 you have to know what procedures those are. (you will know later)
???
GameObjectDeclareReferencedObjectsGroup.Update()
???
GameObjectBeforeConversionGroup.Update()
GameObjectConversionGroup.Update()
GameObjectAfterConversionGroup.Update()
???
GameObjectExportGroup.Update()
GameObjectConversionMappingSystem
The conversion world will automatically get one more very special system in addition to all available GameObjectConversionSystem
, called GameObjectConversionMappingSystem
. (From now on I will say just "mapping system") This system is the boss of any conversion world and can do wonders.
It is a pure "tooling system", meaning that it has empty OnUpdate
and fully expect other systems to get it and do things manually as a part of other system's OnUpdate
. (You can apply this design concept for your own game.)
This is where you could perform GetPrimaryEntity
to communicate with the destination world, or create more (secondary, etc.) entities. When you want to perform conversion, you would have to specify the destination world obviously. That world is used as a constructor of mapping system for use while in conversion.
From your conversion systems, you can GetOrCreateSystem
to get the mapping system on your own but if you subclass GameObjectConversionSystem
that already grab the mapping system for you, along with useful protected
methods to use it.
Let's convert with ConvertToEntity
We know enough to perform conversion. Let's just use Unity's built-in conversion systems for now. That is TransformConversion
and MeshRendererConversion
, therfore make sure the input has Transform
, MeshFilter
, and MeshRenderer
so you see some results.
To select some game objects in the scene to import to conversion world, we can use ConvertToEntity
component. On Awake
, it will use some criterias to select what to import. So if I just attach one to a GameObject
like this, then in the conversion world, I would have an Entity
with these ECS component objects : Transform
, Hello
.

As soon as you enter play mode, on Awake
you get :


This is definitely not ready for drawing, as it has no RenderMesh
yet. But you see how TransformConversion
built-in system see Transform
component object and produce LTW / TR in the primary entity before destroying the conversion world.
If I add some scale :

The conversion result I get this Entity
instead :


Because TransformConversion
conversion system code see that Transform
has a non-identity scale, it decided to add additional component to the primary entity.
Let's additionally see MeshRendererConversion
in action by having MeshFilter
and MeshRenderer
on the convert target.


I get a correctly converted cube alongside my pure data bouncing cube! The conversion world could also assign entity name for you because the conversion world knows Transform
, it is easy to follow back to .gameObject.name
before the conversion world is destroyed.

The "and destroy" original GameObject
behaviour is not one of the the conversion world flow. It is just a thing that ConvertToEntity
perform additionally. Because it think you wouldn't want duplicate thing on your scene if the conversion gives out an equivalent result. I would have 2 cubes at the same position if not for it destroying, one from MeshFilter
and MeshRenderer
, and another from Hybrid Renderer rendering from RenderMesh
and LocalToWorld
.
If you don't like this, you can "just convert" with GameObjectConversionUtility.ConvertGameObjectHierarchy
. But that's for later to learn.
Hierarchy conversion
ConvertToEntity
actually submits all child GameObject
to the conversion world together, unless stopped with ConvertToEntity (Stop)
.

The conversion world get : A B E F G H. Each one with component objects imitating the original one. Transform
component object is the key here because it has .parent
, the conversion world would be able to figure out an entire hierarchy tree.
Adding ConvertToEntity
to D will produce warning that it is not doing anything. Adding ConvertToEntity
to B will not produce any warning but give equivalent result as it is already imported to conversion world by ConvertToEntity
at A.
ECS has a component named Parent
with a single Entity
field to represent transform hierarchy. LocalToWorld
calculated will always respect the Parent
. If Translation
is 0, it is not the case anymore that LocalToWorld
will be all 0 at row 1 2 3, column 4, as if it has a Parent
it will be at the parent instead.
And the conversion system responsible to populating Parent
is none other than TransformConversion
system that give us LocalToWorld
and friends.. let's see the result :

Among A B E F G H, let's guess how many chunks? (We must be aware of data no matter how magical the conversion seems to be!) Assuming that none of them has any non identity scale in the Transform
(so you don't get some with NonUniformScale
).

It's 3.. why?

You may expect all of the to have Parent
, and in the case of A, it could simply has Entity.Null
inside it. But Unity choose to not add a component that is meaningless at all so queries are efficient with ComponentType.Exclude
which could benefit in a bigger game. (You can imagine the transform systems could ignore some matrix work if that entity doesn't have any parent in the first place.)
- No parent, has children : A
- Has both parent and children : F
- At leaf, only has parent : B E H G
You see other internal components/system states added : LocalToParent
, Child
, PreviousParent
. Which you don't have to care. They would eventually help you get the right LocalToWorld
.
When using conversion it maybe useful to be aware of how many different archetypes that would be produced. For example if you have a huge tree of game objects, and some of them are just empty for wrapping, some of them really has MeshFilter
and MeshRenderer
for conversion. You could imagine you get at least 6 chunks out of it. (3 no parent/both/no child * 2 has RenderMesh
or not) And it would get even more fragmented if you have different mesh and materials, because of ISharedComponentData
RenderMesh
. Anyways, this kind of thinking is already required in non ECS Unity when you are trying to optimize for dynamic/static batching, or GPU instancing, or SRP batchers. With Entity Debugger, you see more clearly how many things are together efficiently.
The "and destroy" behaviour also changed a bit in a presence of stops, the stopped tree will properly get detached and preserved. Because you don't get an entity equivalent on that part, you probably want to still see them.

Disabled GameObject conversion
You get Disabled
component tag added for all GameObject
that are disabled. (Not that they wouldn't be converted.) Convert and destroying the top Cube below get you 5 Entity
with 3 of them Disabled
.

You can also put Convert To Entity (Stop) somewhere inside the disabled chain, the stop component is still able to be existence queried by the conversion code even when the object is not active. If I put stops on Cube(1) and Cube(4), I get 3 Entity
out with Cube (2) and Cube (3) Disabled
and Cube not Disabled
.
Convert and inject
What if :
- You want to keep the original game object.
- You also want the component objects exported from the conversion world to the primary entity, before the conversion world is destroyed. (In addition to new components converted)
Before, supposed I have some nonsense such as Hello
or LineRenderer
here, they would get into conversion world. But since there are no conversion system that would look at Hello
or LineRenderer
and do anything to the primary entity, they would be lost on conversion world's destruction.

But withh "Convert and inject" mode, this is possible.


So I get LocalToWorld
like usual because I have Transform
imported into my conversion world. But additionally, Transform
Hello
and LineRenderer
got exported out before the conversion world is disposed. Coupled with a no-destroy behaviour of this mode, it means that you get an Entity
that has a way to go back to your original GameObject
. If we destroyed the original, em.GetComponentObject<Transform/LineRenderer>
on this primary entity would get you null
.
So you see the reason now why the previous mode make sense to destroy the original (so you don't see duplicates if the conversion works) and why this inject mode make sense to keep the original (or else the exported component objects would all leads to null
and are useless).
And that means if I have a cube with MeshRenderer
and MeshFilter
but choose the inject mode, I will get 2 cubes rendered at the same position. See this, a cube of 2 * 6 * 2 tris (+2 tris for blue background probably). With convert and destroy, it would still say 26 tris because it got converted to what Hybrid Renderer could use. But with inject mode, it goes up to 50 tris. Because it keep the original and you also get the product from conversion that Hybrid Renderer use.


Therefore this is not a good usage of inject mode. Inject mode is for when you expect the system working on conversion result to be able to check back the original object, which Hybrid Renderer obviously no longer need to.
Manager of everything : Using systems with classic MonoBehaviour
Here's cool thing you could do with inject mode! You can make a hybrid ECS game now. You can view inject mode as turning your object to be system-workable, even if you don't have any good conversion systems. Just the component objects able to escape from conversion world is useful enough. Then you can ditch logic in all MonoBehaviour
and use systems to control them instead.
You can add MonoBehaviour
to GameObject
like they were IComponentData
. There maybe a "tag MonoBehaviour
" just so you could identify them! Previously you may inherit MonoBehaviour
class to get exposed fields for reuse, now you may consider adding multiple components instead like in ECS where struct
could not be inherited.
I gain no performance benefit, but great flexibility in adding systems and use ECS's query power.
Maybe you used to have a "manager optimization" where you omit Update()
on each object, and instead the manager that do has an Update()
has a List
of all things and the manager iterate and perform work on them to save Update
cost.
The system working on component object is exactly that! Except the query is much more flexible. You can keep pumping out managers that work on subset of GameObject
you want. You can divide work easier between your teammates. You can rearrange work order by just changing UpdateBefore/After
instead of using the script execution order number hell.
For example this regular UGUI buttons. I want to use systems to give logic to them, like alternating interactable
so they appears blinking between greyed out state and back to normal.

All buttons has convert and inject, one of them missing the BlinkingButton
"tag" GameObject
. It has no code at all but I want to use only the type in the query.
Conversion result, we get 2 chunks, one chunk with 3 entities (the orange bar moved a little bit to the right, so this chunk has 3/160) and one with 1 entity.

Then a system like this could plug into ECS database and get all the buttons with blinking component from the scene. It is very useful that these GameObject
could "came from nowhere" with the power of ECS query, gone are the days that you have to declare GameObject[]
and collect correct things. It is already a nice tool to make normal Unity game even if you have no interest in ECS.
using Unity.Entities;
using Unity.Jobs;
using UnityEngine.UI;
[UpdateInGroup(typeof(PresentationSystemGroup))]
public class BlinkingButtonSystem : JobComponentSystem
{
EntityQuery blinkingButtonQuery;
protected override void OnCreate()
{
base.OnCreate();
blinkingButtonQuery = GetEntityQuery(
ComponentType.ReadOnly<Button>(),
ComponentType.ReadOnly<BlinkingButton>()
);
}
//It is not a good idea to have data in system!
float collectTime;
bool on;
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
collectTime += Time.DeltaTime;
if (collectTime > 0.2f)
{
collectTime -= 0.2f;
on = !on;
}
Button[] buttons = blinkingButtonQuery.ToComponentArray<Button>();
foreach (var b in buttons)
{
b.interactable = on;
}
return default;
}
}

Entities.ForEach
could also use component objects, just don't use ref
or in
, and prevent it from using Burst and use Run
instead of Schedule
so it is not trying to put reference type on thread. You can ditch the GetEntityQuery
this way.
Entities.WithAll<BlinkingButton>().ForEach((Button b) => { b.interactable = on; }).WithoutBurst().Run();
You could now use systems in a game that is not at all migrated to ECS or partially migrated. Just convert with the inject mode so you have Entity
for query in the system.
Inject mode conversion importing rule
The previous example has all the buttons receiving inject conversion. What about the Text
inside those buttons?
It is a bit different from destroy mode where it submits all children to the conversion world. This time, the children are all ignored even if they also have ConvertToEntity
with inject mode. Only the top one get convert and injected. So no Entity
produced that would have Text
component object.
To demonstrate this rule further, if I also convert the Canvas
parent in addition to all the buttons..


I don't get blinking buttons anymore since I seems to only get Canvas! Just for fun to recap the destroy mode child submit, I could try change the mode of the Canvas conversion back to destroy so I get "pure" entity with no component objects..

You see it all the way down to texts, and component objects are all destroyed together with the conversion world without exporting out to primary entity.
IConvertGameObjectToEntity
Making conversion system (subclassing GameObjectConversionSystem
) indeed allow you to work on everything in the conversion world. But what if you want a per-type behaviour? Whenever it got converted, something should happen.
Unity has a yet another built-in conversion system called ConvertGameObjectToEntitySystem
. This system would iterate through all GameObject
followed back from conversion world. Then use GetComponents
and see if any got IConvertGameObjectToEntity
. Then call .Convert
on it. You can put any kind of logic in here.
using Unity.Entities;
using UnityEngine;
public class BlinkingButton : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
}
}
entity
is the primary entity resulting from the GameObject
that this component is on. Recall that "primary entity" is associated with one GameObject
or any of its MonoBehaviour
component. dstManager
is the manager of destination world. You can do whatever you like completely unrelated to the conversion. You want 500 useless Entity
created per each BlinkingButton
converted? You can!
You can use this chance to initialize more data with dstManager.Add / SetComponentData
. It's important that you are kind of "remote controlling" the destination world while you are currently working in conversion world while using dstManager
. Take the name literally, because there is an another manager here in conversion world.
Writing a conversion system that Entities.ForEach
and initialize the same thing maybe more efficient because you could use Burst and jobs, if the initializations are similar. But this way you can use fields serialized on your MonoBehaviour
to customize each individual entity generated at will.
The conversionSystem
is actually the ConvertGameObjectToEntitySystem
that call this Convert
in the first place, but casted back to the base type GameObjectConversionSystem
(name confusingly similar, watch out). The purpose is so that you can use the mapping systems to perform work. From here on you will meet many methods on the mapping system.
LinkedEntityGroup
LinkedEntityGroup
is a dynamic buffer of Entity
. Normally it causes :
Instantiate
on oneEntity
would be able to instantiate many entities at once according to the list ofEntity
in that buffer, and also setup similarLinkedEntityGroup
again for you. (According to newly instantiated entities, but saying the same relationship as old one.) Notice that instantiation is possible even before we get to know a component namedPrefab
.DestroyEntity
will also destroy them together if the destroy target is the parent one withLinkedEntityGroup
. So just like pressing delete on theGameObject
having children in the editor.entityManager.SetEnabled
to attach/detachDisabled
component so it won't be queried anymore, will also do it to everything in the buffer. So it works more like disabling a tree ofGameObject
in the editor.
It will not work recursively if it found another LinkedEntityGroup
while performing instantiation/destroy/disabled on all linked enties.
How it perform operation on all linked entities seems to vary. Instantiate
and SetEnabled
have new behaviour as soon as the buffer is detected, and perform operation on all members at once, and nothing more. That means the buffer must include itself as one of the "linked entity". (Linked to.. itself) However DestroyEntity
works even if the buffer didn't contain itself, it destroy the requested Entity
first then iterate through the buffer to add more to destroy.
Note that this is different from Parent
which is used to compute LocalToWorld
that make sense. (But they are often used together.) That one works recursively, and cyclic dependency is not allowed.
Getting LinkedEntityGroup from non-prefab conversion
So far, we get no LinkedEntityGroup
at all with ConvertToEntity
on either mode. So destroying the resulting entity (let's say A from the earlier) will not chain-destroy everything. Maybe you are expecting this or not. But they think this should be the default behaviour. (Unlike with Parent
, where Unity link up according to hierarchy while converting Transform
by default.)
If you want the buffer added and populated, on the mapping system while converting, use :
public void DeclareLinkedEntityGroup(GameObject gameObject)
The the primary entity of that object will get the buffer, containing all its children (recursively, linearized) at the destination world.
public class CubeConvert : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
conversionSystem.DeclareLinkedEntityGroup(this.gameObject);
}
}

And here they are. It includes itself in order to support Instantiate
and SetEnable
as explained.

There are other places that Unity perform the linked processing by default, but not here in ConvertToEntity
. For example while converting a prefab asset. (next)
Size alert!
You may think 16kB per chunk was a lot, you can fit as much as 2000 Entity
in it. But as soon as you play with conversion that involves hierarchy, LinkedEntityGroup
buffer and its system state friends (Child
, etc.) could really take up space per entity since each buffer element is an Entity
(8 bytes).
This 12 chunks could only hold 45 Entity
each (capacity) despite it is just a simple hierarchy with very few custom component! Now that's a lot down from 1000 range.

As of version 0.5.1, the TypeManager.cs
source code says any buffer type without [InternalBufferCapacity]
(LinkedEntityGroup
is like that) will have capacity 128/size
. LinkedEntityGroup
is a buffer of Entity
8 bytes each, therefore capacity is 128/8 = 16
.

This implies :
LinkedEntityGroup
(or any buffer without capacity specification) take from the get go 128 bytes perEntity
. (equivalent to 32int
/float
) That's why capacity in a chunk get so small.- It's not good to have more than 16 children (counted recursively, not just one level down) in any of your
GameObject
for conversion, or else the remembered linked entities have to move out of nicely packed chunk to heap memory. Maybe Unity think 16 is the unlikely number and 8 is maybe too likely. LinkedEntityGroup
is only added for prefab conversion unless you have a declare linked on normal conversion. Therefore normally, you just need to keep a watch on your converted prefabs how manyGameObject
it got.- Remember that at runtime, all the nested prefab and variant workflow doesn't matter and the system see it as a single prefab. It is not that if you have a nested prefab, they would be excluded from
LinkedEntityGroup
and begin their own.
Lastly a chunk of 16kB means you can have about 60 chunks in 1 MB, for perspective. Therfore if the capacity get down to about 45 like above example, then you can fit about 2700 converted GameObject
in 1 MB. Maybe capacity of 45 is nothing to worry about after all. (It depends)
Creating additional entities
A theme of conversion is that one GameObject
results in one Entity
that is its "primary entity". On the mapping system, call CreateAdditionalEntity(gameObject)
to create more Entity
that should came from any GameObject
. Those entities are now "secondary entity of that game object", etc.
Supposed that the new design is so whenever GameObject
with CubeMultiple
get converted, it gets 2 additional entities.
public class CubeMultiple : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
Entity additional1 = conversionSystem.CreateAdditionalEntity(this.gameObject);
dstManager.SetName(additional1, $"{this.name}_Add1");
Entity additional2 = conversionSystem.CreateAdditionalEntity(this.gameObject);
dstManager.SetName(additional2, $"{this.name}_Add2");
}
}
Note that Convert
call works recursively, the ConvertToEntity
is at the top CubeHead.

Result. Note that the additional entity really is newly created. There is no component at all unlike those converted from component objects like Transform
. (See the chunk with 2008 capacity.)

You may realize you can do the same thing by commanding the destination entity manager dstManager.CreateEntity()
. But it is not just a matter of making things proper.
Query all entities that "came from" a single source
On the mapping system, call GetEntities(gameObject/component)
. Because we made additional entities and make the conversion world know they came from the same certain thing, it is now possible to ask for them all. For example, if I have a reference to GameObject
CubeMultiple and use GetEntities
I would be able to get both its primary entity and 2 additional entities.
You don't have to worry about whether they have been created yet / worry about Convert
order, because on this kind of call it has a routine to make sure to Convert
if not yet. (This also applies to GetPrimaryEntity
)
Affecting linked entity group
Recall DeclareLinkedEntityGroup
. By creating additonal entity the proper way the conversion system know their association with some GameObject
in-conversion. The result is that they are included in the linked entity group generate from the declare.
The previous example where CubeHead has DeclareLinkedEntityGroup
on itself, the result now includes additional entities generated from CubeMultiple.

It wouldn't work if you just cheat your way and use dstManager
to create entity. By doing that, it sounds more like converting CubeMultiple causes "side effect" of adding more Entity
to destination world, rather than CubeMultiple really becomes multiple entities. It looks like it should also make things play well with live link. (explained later)
Declaration
Maybe you want an Entity
generated per asset too, not just per things on the scene. Good news because primary entity could also be associated with an asset, not just GameObject
(or MonoBehaviour
) In other words, if you GetPrimaryEntity
inputting those asset to the mapping system you will get a representative Entity
of the asset.
Because you can't put ConvertToEntity
on an asset file on Project panel, you need some way to "declare" that you want them as an entity after the conversion.
When could you declare them?
Before any conversion take place. This is know as the discovering phase. The API will error otherwise if you do so while in conversion. This means while in conversion, you can nab the primary entity of the converted asset, by using GetPrimaryEntity
of the mapping system inputting the original asset, to assign to any component in the Entity
of the non-asset stuff. Otherwise it maybe a bit difficult to query them for use later if you are not doing it now in conversion.
IDeclareReferencedPrefabs
In the discovering phase, first all participating GameObject
will be scanned if any of its component got IDeclareReferencedPrefabs
. In here you can declare by adding prefab assets (not other asset yet) to the given list. You can bring prefab asset by the serialized exposed GameObject
field connecting to prefab asset in your Project panel.
Declare within conversion systems
After the scan for that interface, recall the 5 conversion groups. All conversion system in the very first GameObjectDeclareReferencedObjectsGroup
will get updated. In here, you have a yet another chance to declare things as much as you like, prefabs and this time any other assets.
public class GameObjectDeclareReferencedObjectsGroup : ComponentSystemGroup { }
public class GameObjectBeforeConversionGroup : ComponentSystemGroup { }
public class GameObjectConversionGroup : ComponentSystemGroup { }
public class GameObjectAfterConversionGroup : ComponentSystemGroup { }
public class GameObjectExportGroup : ComponentSystemGroup { }
Using conversion system meaning that you have the mapping system at hand. There are several methods in the mapping system you can use for declaration. Where could you get Project panel things from system code though? You can query for any component object in the conversion world that may have the asset you want connected to exposed field, for example. But at which point you may start to wonder why didn't you use IDeclareReferencedPrefabs
in the first place when you have to make some asset "holder" anyways. Use system-based declaration in more customized cases and stick to IDeclareReferencedPrefabs
most of the time.
Then when time comes for normal conversion, you can combo with IConvertGameObjectToEntity
to get a hold of the just-converted Entity
of an asset/prefab you want by getting its primary entity, using that same before-convert prefab reference as a key to get it.
public class BlinkingButton : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
public GameObject myPrefab;
//This first
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(myPrefab);
}
// |
// V
//Then GameObjectDeclareReferencedObjectsGroup <- last chance to declare stuff
//Then GameObjectBeforeConversionGroup
//Then GameObjectConversionGroup, which contains ConvertGameObjectToEntitySystem that run the following Convert
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
//Get primary entity of the prefab asset, to assign to primary entity converted from this component.
Entity prefabAssetEntity = conversionSystem.GetPrimaryEntity(myPrefab);
//Remember it, etc.
}
// |
// V
//Then GameObjectAfterConversionGroup
}
If you want to see the error about violating declaration moment rule, notice that at the Convert
of IConvertToGameObjectToEntity
moment you also have a chance to use mapping system. (To add additional entity, to travel to other primary entity, etc. as said earlier) But if you use this mapping system to declare more things you will get errors. It's too late!
This is getting demystified now :
??? <- IDeclareReferencedPrefabs calls.
GameObjectDeclareReferencedObjectsGroup.Update()
??? <- End of discovery phase, no more declares to assets.
GameObjectBeforeConversionGroup.Update()
GameObjectConversionGroup.Update()
GameObjectAfterConversionGroup.Update()
???
GameObjectExportGroup.Update()
By the way that declare deadline only applies to declaring assets and prefab assets. You can still DeclareLinkedEntityGroup
while in conversion.
Declaring ANY asset
On the mapping system, use DeclareReferencedAsset
.
You will get an Entity
with tag component Asset
, plus a component object of exactly that Unity asset type with reference to the asset, inside conversion world. (not destination world)
For example I could have my Entity
+ Asset
+ SpriteAtlas
entity created from declaring DeclareReferencedAsset(mySpriteAtlas)
. The primary entity of it on destination world is completely empty though. It is still not clear what is the purpose of this system. Perhaps it expect you do perform Asset
to anything conversion on your own or else you get empty entity out.
Declaring prefab asset
Add it to the list as a part of IDeclareReferencedPrefabs
. Or in the case of declaring inside GameObjectDeclareReferencedObjectsGroup
group, using the mapping system, use DeclareReferencedPrefab
.
But this time it is not as dumb as getting Asset
and GameObject
as a component object with it. This time, you really get a prefab entity on destination world.
A primer what an Entity
prefab is. If you attach Prefab
to any Entity
, it would stop showing up on query unless explicitly asked to, works almost like Disabled
except when using Instantiate
, it strip out the Prefab
tag. (You wouldn't want the instantiate to produce yet another prefab...)
The purpose is different, we hidden them from query not because we want to make them "inactive", but because they aren't supposed to be active. They are just blueprints to create new data fast. On creating a query there are 2 separated mode "include disabled" and "include prefabs". So please use it as the library intended. (Do not use Prefab
to hide from query, etc.)
Together with LinkedEntityGroup
which affects EntityManager.Instantiate
as explained earlier, you can really feel like instantiating a prefab in ECS!
So by declaring a prefab asset, you have added a new primary entity related to that asset with both Prefab
and LinkedEntityGroup
properly setup at the output.
Note that by placing ConvertToEntity
on Hierarchy they would just turned out to a regular Entity
from what we have seen so far, because things on hierarchy aren't asset. We don't get any Prefab
or any LinkedEntityGroup
. No matter if they are blue named on the Hierarchy or not.
Prefabs are asset file, and you can't paste ConvertToEntity
to run on assets in Project panel. The ECS library determine if it is a prefab or not by seeing that if it is on the scene or not. And everything pastable with ConvertToEntity
are on the scene, obviously.
Prefab asset conversion
You convert a prefab asset by declaring it like explained. I would like to go into details what are differences than when you convert GameObject
on the Hierarchy. Instantiated from prefab asset or not, doesn't matter as long as they are on Hierarchy. It is only counted as asset if they link to your Project panel's assets.
Automatic LinkedEntityGroup : for instantiation
With the following setup on the prefab asset :


[GenerateAuthoringComponent]
public struct PrefabConversion : IComponentData
{
public Entity prefab;
}
We would get :

- If we convert CubeHead instantiated onto scene, we wouldn't get any
LinkedEntityGroup
. This behaviour is only for prefab asset, it's intention is of course to get it ready for instantiation. (And also you get chain-destroy behaviour of the instantiated entity, since instantiation duplicates even this linked entity group.) LinkedEntityGroup
contains everyone down the tree and also itself. You may expect it to not contain Cube 3 and 4 which is not the case. Conversion "linearize" the prefab. It is trying so recursive algorithm is not needed, just go through linked entity group linearly when instantiating.- The reason that it also include itself in the buffer because mass
SetEnable
could do it all at once, rather than at the requestedEntity
first then do it for each linked entity. - All entities including the head get
Prefab
. But we will use just the head forInstantiate
.Prefab
actually doesn't matter forInstantiate
. It'sLinkedEntityGroup
that make instantiation work in group. You can instantiate any entity.
Automatic LinkedEntityGroup : for chain-enable
The previous section explained that only at the head of prefab you get LinkedEntityGroup
for the purpose of instantiation.
Additionally, anywhere in the prefab that it found disabled GameObject
, it get a LinkedEntityGroup
containing all children recursively.
The purpose of this one is so that you can use SetEnable
on the instantiated Entity
a single time and remove Disabled
from multiple entities at once. Disabled
conversion still follow the same rule as when converting a non-asset GameObject
, this added LinkedEntityGroup
further helps you.
Can't stop won't stop
You cannot put Convert To Entity (Stop) somewhere in the prefab in hope that it will not be included in the result.
Fun quizzes
With all conversion rules so far, see if you could get the conversion result right regarding to following components : Parent
(remember that this is for transform systems), Prefab
, Disabled
, LinkedEntityGroup
(and its content). Child
and friends will appear according to Parent
after a round of update by transform systems.

Cube : Prefab, LinkedEntityGroup (Cube,1,2,3,4)
1 : Prefab, Parent (Cube)
2 : Prefab, Parent (Cube)
3 : Prefab, Parent (2)
4 : Prefab, Parent (2)

Cube : Prefab, Prefab, LinkedEntityGroup (Cube,1,2,3,4)
1 : Prefab, Parent (Cube)
2 : Prefab, Parent (Cube), Disabled, LinkedEntityGroup (2,3,4)
3 : Prefab, Parent (2), Disabled
4 : Prefab, Parent (2), Disabled
Remember that you always get itself in one of the element of LinkedEntityGroup
, including for-enable ones. And therefore if you have any disabled GameObject
even if it is at the leaf, you always get LinkedEntityGroup
of at least 1 element that is itself. (You may think it doesn't matter, but it will fragment chunks a bit more)

Cube :
1 : Parent (Cube)
2 : Parent (Cube)
3 : Parent (2)
4 : Parent (2)

Cube :
1 : Parent (Cube)
2 : Parent (Cube)
3 : Parent (2)
4 : Parent (2)
Blue name (linked with prefab asset) or not doesn't matter, it is not an asset. It won't follow LinkedEntityGroup
conversion rule.

Cube :
1 : Parent (Cube)
2 : Parent (Cube), Disabled
3 : Parent (2), Disabled
4 : Parent (2), Disabled
You cannot chain-enable starting from primary entity of Cube (2), because there is no LinkedEntityGroup
generated for you. You must declare linked manually.
Remapping behaviour of prefab instantiation
Only when calling Instantiate
with an entity with LinkedEntityGroup
, an entity remap will take place. (Read more here : https://gametorrahod.com/entity-remapping/) You know converting a prefab asset make LinkedEntityGroup
available, perfect.
So it is possible to bake an Entity
value somewhere in the prefab entity you get from conversion, and when instantiating, that baked value would came back to life, making sense in the instantiated context.
From the previous situation, if I add a new conversion script to SpecialCube so its Convert
get called as a part of making CubeHead prefab entity, an intention is to remember the Entity
that would be generated from its Cube (3).

public class SpecialCube : MonoBehaviour, IConvertGameObjectToEntity
{
public GameObject itsChild;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponent<LinkedEntityGroup>(entity);
var leg = dstManager.GetBuffer<LinkedEntityGroup>(entity);
leg.Add(conversionSystem.GetPrimaryEntity(itsChild));
}
}

Checking the Prefab
entities got from conversion, it is expected that this manual LinkedEntityGroup
(I could just bake a single Entity
, but any buffer of Entity
works too.) has reference to its Prefab
peer.

Remapping will take place to this entity hidden in the prefab as long as it could find something to remap to among the LinkedEntityGroup
at top where you use with Instantiate
. Therefore it is useful to "wire up" the Entity
reference while in conversion before conversion turning it into a Prefab
, so when you Instantiate
it you get a ready to use relationship.
Remapping won't occur if Instantiate
to something without LinkedEntityGroup
because in that case there is no peer to look for remapping Entity
value.
What about GameObjectExportGroup
With 4 out of 5 groups introduced and their purpose, this is the last one. So it looks like that the conversion could be retooled not for use, but for export instead. Normally, the export group is not included in the conversion pipeline. Only when asked to export that they are scanned and collected to be at the end.
If your conversion system is in here, expect not only all conversions has finished, also you can see LinkedEntityGroup
available and buffer populated and Prefab
tag added to all declared prefabs at this stage, so you can use it. (use for instantiation, etc.) Of course you should not convert anymore in a group named "export group".
What is an "export" remains a mystery, maybe when Unity team made some documentation I could understand. For now you can just think this group is not useful for your everyday conversion. And it has to do something with Asset
entity result from declaring assets, explained earlier.
??? <- IDeclareReferencedPrefabs calls.
GameObjectDeclareReferencedObjectsGroup.Update()
??? <- End of discovery phase, no more declares to assets.
GameObjectBeforeConversionGroup.Update()
GameObjectConversionGroup.Update()
GameObjectAfterConversionGroup.Update()
??? <- LinkedEntityGroup added to declared linked, LinkedEntityGroup + Prefab added to declared prefabs back at discovery phase.
GameObjectExportGroup.Update()
What is DeclareDependency on the mapping system
There is yet another unexplained declare. But I don't know about this. It looks like something that make live link (explained later) work better if you do it correctly.
Demo on declaring prefab asset and using it

If I convert and destroy this purely MeshFilter
and MeshRenderer
tree of cubes, I would get 2 chunks out of it.


But I have made this into a prefab asset and delete it from the scene. I would like to instead convert this to a hidden Prefab
entity for use with Instantiate
later.
A new plan is the following : a new SpawnHere
MonoBehaviour
"tag" component should be custom-converted into spawning positions of the entity prefab

What if the code is like this for each (it is fine for 3 GameObject
to declare the same prefab, the conversion system knows not to add duplicates) :
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
public class SpawnHere : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
{
public GameObject forDeclare;
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(forDeclare);
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
}
}
So at discovery phase it is declared so the asset's primary entity would already been created, which would looks just like prior result except it has Prefab
and LinkedEntityGroup
so it is hidden, ready for spawn. Spot the difference from before! (You cannot see this cube, since they are hidden from query of Hybrid Renderer thanks to Prefab
though it satisfy the RenderMesh
and LocalToWorld
conditions.)


After conversion, I could grab the Prefab
and instantiate at the Translation
of converted 3 spawn heres. Since the SpawnHere
is convert and destroyed, it is kinda scary to query from either LocalToWorld
or Translation
as many other Entity
may also have these.

So let's add a tag in conversion to indicate that they really came from SpawnHere
:
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
public struct SpawnHereEcs : IComponentData
{
}
public class SpawnHere : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
{
public GameObject forDeclare;
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(forDeclare);
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponent<SpawnHereEcs>(entity);
}
}

Additionally, to avoid having to fumbling for the prefab entity later, let's just put the prefab asset's primary entity to stick with the tag. (So technically not a tag anymore) The key to get that entity is the original prefab used at declaration.
using System.Collections.Generic;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
public struct SpawnHereEcs : IComponentData
{
public Entity thatPrefabEntity;
}
public class SpawnHere : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
{
public GameObject forDeclare;
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(forDeclare);
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
Entity prefabEntity = conversionSystem.GetPrimaryEntity(forDeclare);
dstManager.AddComponentData<SpawnHereEcs>(entity, new SpawnHereEcs {thatPrefabEntity = prefabEntity});
}
}
Later, a system looking for SpawnHereEcs
entities would then spawn at those positions. Also remove SpawnHereEcs
so it wouldn't spawn repeatedly.
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class SpawnSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
Entities.ForEach(
(Entity e, in Translation t, in SpawnHereEcs she) =>
{
Entity instantiated = EntityManager.Instantiate(she.thatPrefabEntity);
//Set to the same translation.
EntityManager.SetComponentData(instantiated, t);
EntityManager.RemoveComponent<SpawnHereEcs>(e);
})
.WithStructuralChanges().Run();
return default;
}
}
And there you have it!


The instantiated one all have LinkedEntityGroup
just like the Prefab
. And notice it have a rather nice editor there with Select buttons.
Bonus : Trying to get even smarter, how about no system following to spawn at all but spawn the cubes right at the conversion? This way we don't need the SpawnHereEcs
tag, and we know TransformConversion
is already done at this point so we know where to spawn from each spawn here's primary entity. So just use the given dstManager
to perform instantiation right here. It would feel just like SpawnHere
MonoBehaviour
converted into prefab instances, instead of previously where they converted into spawn positions, prefab converted into prefab entity, then finally a system use those position to instantiate.
Bonus point if you could figure out why this won't work properly before scrolling down!
using System.Collections.Generic;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
public class SpawnHere : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
{
public GameObject forDeclare;
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(forDeclare);
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
Entity prefabEntity = conversionSystem.GetPrimaryEntity(forDeclare);
var spawnPosition = dstManager.GetComponentData<Translation>(entity);
Entity instantiated = dstManager.Instantiate(prefabEntity);
dstManager.SetComponentData<Translation>(instantiated, spawnPosition);
}
}

It almost work. But it should spawn 3 more cubes each. GetPrimaryEntity
with the declared prefab asset certainly works at this point since it has already past the discovery phase. GetComponentData<Translation>
also should work as Transform
has been converted to that since GameObjectBeforeConversionGroup
group as we know before.
But recall that the LinkedEntityGroup
and Prefab
for all the declares are only finalized at :
??? <- IDeclareReferencedPrefabs calls.
GameObjectDeclareReferencedObjectsGroup.Update()
??? <- End of discovery phase, no more declares to assets.
GameObjectBeforeConversionGroup.Update()
GameObjectConversionGroup.Update() <-- YOU ARE HERE
GameObjectAfterConversionGroup.Update()
??? <- LinkedEntityGroup added to declared linked, LinkedEntityGroup + Prefab added to declared prefabs back at discovery phase.
GameObjectExportGroup.Update()
...just before GameObjectExportGroup
. And therefore because we are currently in GameObjectConversionGroup
, that primary entity is lacking both Prefab
(not a problem) and LinkedEntityGroup
(a problem, since this cause the magic on instantiation to instantiate things other than only the top one). Therefore, you cannot use the prefab you just declared while in conversion because it is not finalized yet. If you didn't know anything about conversion world and just use the conversion tools, this would be quite hard to reason.
[GenerateAuthoringComponent]
Notice the SpawnHere
-> SpawnHereEcs
pattern earlier. It is likely that you want a MonoBehaviour
component that expose some box on the inspector only to migrate that to an equivalent after the conversion. We call the MonoBehaviour
one the authoring component.
We could create a GameObjectConversionSystem
that perform adding equivalent component and migrate data. But doing it in IConvertGameObjectToEntity
is more succint. Because it is right there with the authoring class file.
using Unity.Entities;
using UnityEngine;
//The real ECS
public struct MyData : IComponentData
{
public float data1;
public int data2;
public bool data3;
}
//For authoring
public class MyDataOnScene : MonoBehaviour, IConvertGameObjectToEntity
{
// Ugly duplicated code
public float data1;
public int data2;
public bool data3;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData<MyData>(entity, new MyData
{
// Tedious work
data1 = data1,
data2 = data2,
data3 = data3,
});
}
}


What's even better than this, is to just declare the ECS part and have IL magic make the MonoBehaviour
one for you!
using Unity.Entities;
[GenerateAuthoringComponent]
public struct MyData : IComponentData
{
public float data1;
public int data2;
public bool data3;
}

Note that we didn't declare any MonoBehaviour
yet we get a component nicely named "My Data Authoring" and even "My Data" that leads to the same thing. This is a generated code and it would have a Convert
section similar to what we have written by hand. You cannot double click the MyDataAuthoring
to see generated code, it would lead to your ECS struct
part. It's basically magic!
Currently your ECS fields must be public
for the magic to happen. Something like this is not getting translated to generated mono part yet :
using Unity.Entities;
using UnityEngine;
[GenerateAuthoringComponent]
public struct MyData : IComponentData
{
[SerializeField] internal float data1;
[SerializeField] public int data2;
[SerializeField] public bool data3;
}

There is one more trick to this, it is also able to generate the "declare inspector connected prefab at discovery phase and then grab its primary entity to save into primary entity of the object being converted" pattern I used with SpawnHereEcs
written by hand earlier, by just declaring Entity
field.
using Unity.Entities;
[GenerateAuthoringComponent]
public struct MyData : IComponentData
{
public Entity magic;
}

It turned into GameObject
exposed field! This is equivalent to writing these manually :
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
//[GenerateAuthoringComponent]
public struct MyData : IComponentData
{
public Entity magic;
}
public class MyDataAuthoring : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
public GameObject magic;
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(magic);
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
Entity primaryEntityOfDeclaredPrefab = conversionSystem.GetPrimaryEntity(magic);
dstManager.AddComponentData<MyData>(entity, new MyData
{
magic = primaryEntityOfDeclaredPrefab
});
}
}
And of course you can have a mix of Entity
and float
and others... magic!
Mixing both destroy and inject mode
You may feel like you don't want the inject mode because you get too much component objects and it didn't travel an entire tree. But also the destroy mode cannot convert something down the tree cleanly to ECS. You kinda want something partially injected. And the "stop" component is not answering the need since you do want to convert, not skip that part.
An example : If I build a ship like this out of Transform
, MeshFilter
, and MeshRenderer
, the ConvertToEntity
with destroy mode would be able to cleanly convert it to LocalToWorld
+ RenderMesh
.


I remembered the cube bouncing system that just look for Cube
with Translation
. If I add Cube
to the ship's root it should do the trick. Use the magic we just learn to add Cube
component as a part of conversion.
using Unity.Entities;
[GenerateAuthoringComponent]
public struct Cube : IComponentData
{
}

But if I have a particle system on its exhaust :

When a component object ParticleSystem
get into the conversion world, of course it wouldn't survive. The result I want, since the particle system is on local simulation space, all particles should bounce up and down.
If I add Stop at the particle system then it wouldn't get converted at all and is now unrelated and not even having entity counterpart.
How about adding inject mode inside destroy mode? Now I do get an injected Entity
with component object ParticleSystem
out. But is it in anyway related to the separately converted with destroy mode ship?

The current result didn't say so.

But however with Entity Debugger, it reveals that it did have Parent
and LocalToParent
added as a part of being converted together on the same tree with destroy mode. At the same time seeing component object ParticleSystem
here showing that it works with injected mode. You have mixed both modes!

From our knowledge, Parent
should already get us the correctly following LocalToWorld
. But the particles aren't bouncing since there is no one to use that LocalToWorld
(Not related to Hybrid Renderer this time) So we can just copy back the LTW to Transform
with the copy transform to game object proxy. Doing so will also add GameObjectEntity
. You then see a lone Particle System in Hierarchy following ECS-moved ship. This is like you convert 95% of what you could, then glue the remaining 5% to your fully converted part.

Note that if this ship is a prefab asset, then you declare this ship, the resulting prefab will looks like what came out of only the convert and destroy, that is, missing the particle system.
Companion Game Object
The previous approach did sounds like a hack. The Particle System left over in the hierarchy yet it move together with the converted entity screams "glue".
Companion game object is a more "clean" way of convert and destroying a tree that contains bits of unconvertible objects. It will create a new game object to accompany convertible data and have it host select components you want to save. Making it seems like everything converted nicely.
You do so with the mapping system. Therefore, I will create some script so I could have IConvertGameObjectToEntity
. There is no ECS counterpart of this Ship
. It is just for customizing the convert a little.
using Unity.Entities;
using UnityEngine;
public class Ship : MonoBehaviour, IConvertGameObjectToEntity
{
public ParticleSystem particleCompanion;
public ParticleSystemRenderer rendererCompanion;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
conversionSystem.AddHybridComponent(particleCompanion);
conversionSystem.AddHybridComponent(rendererCompanion);
}
}
The name is companion game object, but you config it with components. You call the mapping system AddHybridComponent
for it to collect not only what GameObject
should become a companion, also what component should remain on that companion to be created (per one primary entity, identified from that component). Therefore I declare public fields of components instead of GameObject
.
In this case of ParticleSystem
, it actually has one more hidden ParticleSystemRenderer
component. So if I forgot the renderer, only ParticleSystem
remains on the companion game object and it wouldn't render. (And also waste CPU simulating particles.)

I have removed ALL the hacks from that Particle System! (Removed ConvertToEntity
with inject mode, removed copy proxy, removed GameObjectEntity
) it is just convert and destroy an entire ship.

Now there is no Particle System left on the Hierarchy this time, YET it is there in the Game view AND also move together. How? Where is it? Even inject mode ConvertToEntity
where you get component objects on Entity
, they should link back to something on the scene.
This is the primary entity of Particle System (under Cylinder, so its Parent
should be Cylinder)

The companion registration of both components automatically leads to their (same) GameObject
's primary entity (This Particle System entity) being inject-converted with all component called with AddHybridComponent
. (You see ParticleSystem
and ParticleSystemRenderer
here)
Those component object still link back to things on the scene... but the trick is that they are just being hidden with HideFlags.HideInHierarchy
. Finally why it moves, notice the CompanionLink
component. That contains the hidden game object on scene with the hide flag. Then there is a system called CompanionGameObjectUpdateTransformSystem
that do the same thing as copy transform to game object except the source is from LTW of entity having CompanionLink
and target is the game object in the CompanionLink
.
So it is the same thing as the glue hack previously after all! But more official and cleaner, and more inline with the conversion system.
It is compatible with prefab!
One thing better than the hacky way is that, it could survive the conversion to Prefab
and is fully instantiable by ECS. (The entire thing, including companion particle system)
That means each instantiation, it would still do it fast for the ship's RenderMesh
converted from MeshFilter
and MeshRenderer
. However it see CompanionLink
component object in the prefab, that is connected with the HideFlag
hidden game object that is also created when you declare and convert the prefab. That will be simply GameObject.Instantiate
to produce a yet another hidden one, but companion-linked to your new instantiated Entity
.
Basically, it feels like EntityManager.Instantiate
somehow perfectly duplicates ParticleSystem
that is not even in ECS database but is hidden on the scene. (Magic!)
First turn the Ship into a prefab asset. Notice that the companion definition would be there in the prefab since it reference its children.

Let's use the authoring magic Entity-GameObject
we learned earlier :
using Unity.Entities;
[GenerateAuthoringComponent]
public struct ShipAndCo : IComponentData
{
public Entity shipPrefab;
}

On entering play mode, I have a Prefab
entity ready on that ShipAndCo
singleton Entity
. Since I know it is a singleton, let's use this kind of system to play with it : each time I press A, instantiate in ECS at random position 0~10.
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
using Random = Unity.Mathematics.Random;
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class ShipAndCoSpawnerSystem : JobComponentSystem
{
Random randomizer;
protected override void OnCreate()
{
base.OnCreate();
RequireSingletonForUpdate<ShipAndCo>();
randomizer = new Random();
randomizer.InitState(seed: 12345);
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
if (Input.GetKeyDown(KeyCode.A))
{
var sac = GetSingleton<ShipAndCo>();
Entity prefabEntity = sac.shipPrefab;
Entity instantiated = EntityManager.Instantiate(prefabEntity);
EntityManager.SetComponentData<Translation>(instantiated, new Translation
{
Value = randomizer.NextFloat3() * 10
});
}
return default;
}
}
I get a duplicated hidden companion particle systems game object as a part of Entity
instantiation. This is pretty cool!

I could see the companion game object being the final piece of puzzle to convert anything to ECS. (Let's ignore the fact that there are some game objects with HideFlag
lurking somewhere.)
GameObjectConversionUtility
If you don't like any of these convert and destroy or convert and inject rules, you can "just convert" on your own with this static
class.
public static Entity ConvertGameObjectHierarchy(GameObject root, GameObjectConversionSettings settings)
But you have to get your GameObject
first, ConvertToEntity
is handy since it select that object the component pasted on to import to conversion world, plus additionally its children based on mode. Now you don't have that. Instead you can input only one GameObject
to this method.
root
is of course what you want to import into the conversion world before updating it. The name says "hierarchy", it knows how to do the following :
- If this
root
is a prefab asset, then you get the same thing as declaring a prefab in conversion explained earlier. So if you don't like declaring and have the prefab entity generated together, you can do it one by one like this. - If this
root
is something on the scene, it converts an entire hierarchy like you addConvertToEntity
at the top of the tree, except it also performLinkedEntityGroup
processing as if you declared linked entity.
The destination world is inside the settings
, which you could use :
public static GameObjectConversionSettings FromWorld(World destinationWorld, BlobAssetStore blobAssetStore)
To get one. Destination world is probably World.DefaultGameObjectInjectionWorld
for most cases, unless you want conversion result elsewhere. ConvertToEntity
always use this target internally, true to the "DefaultGameObjectInjectionWorld
" name.
BlobAssetStore
I guess is when you want the entity produced to have some compoenent with valid BlobAssetReference
field. For now you could left it null
or just new BlobAssetStore
but please also Dispose
it.
Other interesting methods on the utility
public static void ConvertScene(Scene scene, GameObjectConversionSettings settings)
Like ConvertGameObjectHierarchy
, but do it for everything in the scene. It still won't add any LinkedEntityGroup
buffer, except if something in the scene is disabled. Then you will get its disabled representation in Entity
style, ready to be chain-enabled with entityManager.SetEnabled
to only the top entity with the buffer.
This make it a bit superior than pasting ConvertToEntity
on everything in the scene, since that way you cannot have a disabled converted entity result in the destination world. The disabled GameObject
with ConvertToEntity
wouldn't be Awake
to be converted in the first place.
public static World ConvertIncrementalInitialize(Scene scene, GameObjectConversionSettings settings)
public static void ConvertIncremental(World conversionWorld, IEnumerable<GameObject> gameObjects, ConversionFlags flags)
What about these? Notice that the first one returned World
. So it seems (I have not used it) when you are expecting something else to come into the scene later, then you can use the not-disposed-yet conversion world to convert all the new things incrementally.
They are used internally with the live link system, explained next.
Subscene
I am planning to stop now because subscene is probably in active development, and the usage change very soon. I will keep it at overview until the real release of Entities package.
You can now author the game in GameObject
and get Entity
when entering play mode! What else to desire?
- You want to see the conversion result while in edit mode, even though you have carefully design the conversion to give equal result to the original. (e.g.
MeshFilter
+MeshRenderer
=RenderMesh
) - You want to convert everything and you are too lazy to paste
ConvertToEntity
or even useScene
method in the utility. - You want the game pre-converted to
Entity
when shipping. So when loading the scene, it is not loading, thenConvertToEntity
/GameObjectConversionUtility
, then you getEntity
. It is that you load outEntity
from the get go. Load chunk memory out and ready for use. - You want that, but you don't want to do something like pressing "serialize ECS memory" button in editor's play mode to save the memory out for runtime use, and add memory loader code to the scene's start up, etc.
- While in play mode, even though they are all convert and destroyed to pure
Entity
, you don't want them to disappear from Hierarchy like you are in inject mode. YET you don't want the inject mode as you don't want to draw duplicated cube with normal renderer and Hybrid Renderer at the same time. What I am saying that this is scary as you can't touch anything anymore. No cube on hierarchy yet you see things. Is it possible to keep the hierarchy for inspection or some quick changes?

- And even you may want to modify something (like moving
Transform
) and have it re-convert on the fly (toLocalToWorld
/Translation
) after each modification.
These features are possible with the Subscene! The name may say that it is somehow "lesser" than a scene, but that is just because it must be nested in a regular scene. It's ability is much more than regular scene.
To begin, create a GameObject
and add Subscene
component. You will be asked to add a new Scene
asset which store subscene's content. Create one now.

Then you can click Edit and add content, or just double click the subscene scene asset and modify like normal. I have added my 2 cube GameObject
that were needed help from ConvertToEntity
before. When they are in subscene you can remove it as an entire subscene would be converted. It even has a warning that says so.


Now when you enter play mode, you get the best of both worlds. (no pun intended with ECS World
) It is Hybrid Renderer rendered for sure since tris count is not increased from 50 from while in edit mode.

AND it is pickable. Meaning that clicking the Hierarchy could somehow select the preconverted state even though it is already converted and destroyed as you see right now.
Conversely, if you click on the scene view you can also pick the one on Hierarchy!
Notice that orange outline is missing. It is because of I have this option :

SceneView : Live Game State means that I would like the scene view showing converted result. So the scene view is a Hybrid Renderer rendered graphic and is uncapable of showing orange outline. Checking SceneView: Editing State will make the scene view in play mode looks like when you are not in play mode, that is, capable of showing orange outline. Of course the cube still looks the same as MeshFilter
+ MeshRenderer
= RenderMesh
was designed to look equal. But if the conversion somehow paint the cube green, by checking editing state it would turn back to grey.

The top choice Live Conversion in Edit Mode deals with only when out of play mode, so you could choose the choice and see converted state in edit mode. The SceneView: Live Game State being on while in edit mode but with Live Conversion in Edit Mode disabled is useless as you want to see live game state on scene view in edit mode but you hadn't allow it to.
If you move the classical Transform
while in play mode (possible because subscene left the Hierarchy intact from their pre converted state), it would be updated (reconverted, diff-ed) to the converted state seamlessly. This is called the live link.
Comparing ConvertToEntity to Subscene converted entity

The first one is by ConvertToEntity
on the first cube, the second one by placing both in Subscene. Both are based on the destroy mode, so no component object like MeshFilter
will be present on the converted entity like inject mode.


EditorRenderData
, EntityGuid
, SceneSection
, SceneTag
were added. These are all ISharedComponentData
except EntityGuid
. So you don't have to worry about chunk fragmentation as everyone in the subscene would get the same thing. It is used for.. various thing.
Neither ConvertToEntity
nor subscene would add LinkedEntityGroup
buffer to represent the hierarchy inside the subscene. But the subscene way follow the same rule as using ConvertScene
on the utility class, where it would add LinkedEntityGroup
only if something are disabled, so you could enable back together.

It's not a conversion, it's a scene!
They don't want you to think that sub scene is a bunch of ConvertToEntity
, it is really a scene.
That include this behaviour when you change the scene with SceneManager.LoadScene
. GO has ConvertToEntity
while InSubScene1
has nothing but is inside a subscene.

When enter play mode we get both converted as expected. Subscene and ConvertToEntity
are equal to this point.

However when changing to an another scene :

It know to also delete entities related to the subscene! While the ConvertToEntity
really means it, just convert to entity then they are completely disconnected from the scene. This could help with entity lifecycle greatly. (So systems that interested in them, all completely stop until you enter the scene, etc.)
I think DeclareDependency
should be something that make this work more seamlessly in more complex cases.
Subscene differentiation rules
When you touch your "shadow" Hierarchy while in play mode, sure it performs the conversion all over again. But for example while modifying classical Transform
so the reconversion to Translation
take place, you see that all other components that the Entity
get still remains while inheriting a reconverted transforms.
There must be some merging rules we should know in order to design a conversion code / system code that is "live link compatible". (So it helps making/debugging a game, because on actual runtime you cannot touch Hierarchy.) The reconverted world get "diffed" into the currently running world. It knows the reconverted entity is in fact the same as which one in the running world and pair them up to merge. (The EntityGuid
)
Surely there is a limit to this magic. I wouldn't expect everything to work magically when I reparent things or even drag them in/out of sub scene while in play mode...
For now I don't know much, I would add later if I do. But for example if your converted Entity
would ended up receiving more Entity
as its children, you would want the reconversion to not disrupt those children's relation ship while also replacing subset of components on the parent.
This article is too long now. I would add more to subscenes as more features are added. Have fun authoring ECS game in GameObject
!