Notifications
Article
Part 3: Unity ECS - operating on Entities
Updated 3 months ago
1.1 K
3
How to do something with these entities?
Part 1: Unity ECS - briefly about ecs Part 2: Unity ECS - project design Part 3: Unity ECS - operations on Entities Part 4: Unity ECS - ECS and Jobs

The Basics

EntityArchetype - what is it and why you need this.
An EntityArchetype is a unique array of ComponentType. EntityManager uses EntityArchetypes to group all Entities using the same ComponentTypes in chunks. (docs: THERE)
I'll not cover the chunks idea since this series of article is mainly targeted for beginners.

What will we need in the beginning - initialization.

using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Rendering; using Unity.Transforms; using UnityEngine; public class Boot { static int StartEntitiesCount = 10000; // how many entities we'll spawn on scene start public static EntityArchetype archetype1 { get; private set; } public static EntityArchetype archetype2 { get; private set; } public static MeshInstanceRenderer entityLook { get; private set; } // This attribute allows us to not use MonoBehaviours to instantiate entities [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] public static void InitializeBeforeScene() { var em = World.Active.GetOrCreateManager<EntityManager>(); // EntityManager manages all entities in world CreateArchetypes(em); // we need to set archetype first } // This attribute allows us to not use MonoBehaviours to instantiate entities [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] public static void InitializeAfterScene() { var em = World.Active.GetOrCreateManager<EntityManager>(); // EntityManager manages all entities in world entityLook = GameObject.Find("GameObjectWithMeshInstanceRenderer").GetComponent<MeshInstanceRendererComponent>().Value; // no other graphic api for now (06.2018) CreateEntities(em, StartEntitiesCount); } // (..more code collapsed..) }

Just before we create entities, we need EntityArchetype - let's create them.

private static void CreateArchetypes(EntityManager em) { // ComponentType.Create<> is slightly more efficient than using typeof() // em.CreateArchetype(typeof(Position), typeof(Heading), typeof(Health), typeof(MoveSpeed)); var pos = ComponentType.Create<Position>(); var heading = ComponentType.Create<Heading>(); var health = ComponentType.Create<Health>(); var moveSpeed = ComponentType.Create<MoveSpeed>(); archetype1 = em.CreateArchetype(pos, heading, health, moveSpeed); archetype2 = em.CreateArchetype(pos, heading, moveSpeed); // that's exactly how you set your entities archetype, it's like LEGO }

Spawn some entities - finally..

private static void CreateEntities(EntityManager em, int count) { // if you spawn more entities, it's more performant to do it with NativeArray // if you want to spawn just one entity, do: // var entity = em.CreateEntity(archetype1); NativeArray<Entity> entities = new NativeArray<Entity>(count, Allocator.Temp); em.CreateEntity(archetype1, entities); // Spawns entities and attach to them all components from archetype1 // If we don't set components, their values will be default for (int i = 0; i < count; i++) { // Heading is build in Unity component and you need to set it // because default is float3(0, 0, 0), which is position // where you can't look towards, so you'll get error from TransformSystem. em.SetComponentData(entities[i], new Heading() { Value = new float3(0, 1, 0) }); em.SetComponentData(entities[i], new Position() { Value = 1 }); em.SetComponentData(entities[i], new Health() { Value = 100 }); em.SetComponentData(entities[i], new MoveSpeed() { Value = 10.0f }); } entities.Dispose(); // all NativeArrays you need to dispose manually, it won't destroy our entities, just dispose not used anymore array // that's it, entities exists in world and are ready to be injected into systems }

But nothing happen - Let's take a look at systems

public class MoveSystem : ComponentSystem { public struct Data { public readonly int Length; public ComponentDataArray<Position> Position; } [Inject] private Data data; protected override void OnUpdate() { // iterate over all entities and send them to the sky for (int i = 0; i < data.Length; i++) { var pos = data.Position[i]; pos.Value += new float3(0, 1, 0); data.Position[i] = pos; } } }
Ah.. now it's much better, something happen. Let's hurt our entities, so they can reach our Sky.
public class HealthSystem : ComponentSystem { public struct Data { public readonly int Length; [ReadOnly] public ComponentDataArray<Position> Position; public ComponentDataArray<Health> Health; } [Inject] private Data data; protected override void OnUpdate() { // iterate over all entities and hurt these that are too high for (int i = 0; i < data.Length; i++) { if (data.Position[i].Value.y >= 100) { var health = data.Health[i]; health.Value--; data.Health[i] = health; } } } }
Ok, but they are still alive..
public class DeathSystem : ComponentSystem { public struct Data { public readonly int Length; [ReadOnly] public EntityArray Entities; [ReadOnly] public ComponentDataArray<Health> Health; } [Inject] private Data data; protected override void OnUpdate() { // iterate over all entities and kill these which have <= 0 health points for (int i = 0; i < data.Length; i++) { if (data.Health[i].Value <= 0) { PostUpdateCommands.DestroyEntity(data.Entities[i]); } } } }
We've got PostUpdateCommands, why not just use EntityManager since it manages entities? Do you remember what did say in our Part 1?
But actually, you don't want to add/remove components via EntityManager. You'd rather do it after update to not break the group (you'll get an error about accessing deallocated nativearray), so you want to use PostUpdateCommands (EntityCommandBuffer).

What is EntityCommandBuffer and why we need it?

It's pretty good explained in documentation THERE
All structural changes have hard sync points. CreateEntity, Instantiate, Destroy, AddComponent, RemoveComponent, SetSharedComponentData all have a hard sync point. Meaning all jobs scheduled through JobComponentSystem will be completed before creating the Entity, for example. This happens automatically. So for instance: calling EntityManager.CreateEntity in the middle of the frame might result in a large stall waiting for all previously scheduled jobs in the World to complete. See EntityCommandBuffer for more on avoiding sync points when creating Entities during gameplay.
There is mentioned JobComponentSystem that I haven't cover yet, but don't worry, it'll be in next part! EntityManager invalidates existing, injected arrays and component groups, so it'll mess up our Updates. Unlike in JobComponentSystems, PostUpdateCommand(EntityCommandBuffer) in ComponentSystem is set and available automatically. This EntityCommandBuffer allows us to queue up changes to be performed so that they can take effect later in the main thread. Now you're interested in using jobs in ECS for sure. I'll write next part about that.

Is it all? No, are you tired? There is just one more part: Part 4: Unity ECS - ECS and Jobs

Give me some feedback in comment section, don't worry I won't hate you if you show me some "anomaly" in my article. If you enjoy my article - like it and follow me. It'll motivate me to write more articles. See you.

Tomasz Piowczyk
Student Game Developer - Programmer
8
Comments
Tomasz Piowczyk
4 months ago
Student Game Developer - Programmer
NoxalusThank you for this tutorial but I have this error when I try to get the MeshInstance component: "ArgumentException: GetComponent requires that the requested component 'MeshInstanceRenderer' derives from MonoBehaviour or Component or is an interface.".
Yes, it's my mistake, should be GetComponent<MeshInstanceRendererComponent>().Value
0
N
Noxalus
4 months ago
NoxalusThank you for this tutorial but I have this error when I try to get the MeshInstance component: "ArgumentException: GetComponent requires that the requested component 'MeshInstanceRenderer' derives from MonoBehaviour or Component or is an interface.".
Ouch, I hit enter too early... So am I the only one to get this error? In my case, the MeshInstanceRenderer class implements the ISharedComponentData interface, but doesn't derive from MonoBehaviour or Component :O
0
N
Noxalus
4 months ago
Thank you for this tutorial but I have this error when I try to get the MeshInstance component: "ArgumentException: GetComponent requires that the requested component 'MeshInstanceRenderer' derives from MonoBehaviour or Component or is an interface.".
0