Not much has happened since last month in terms of programming breakthrough. I guess we’re just busy adding features to our game. As a fallback article, I’ll share some utility code that we use, instead. These are mainly used in ECS.

BufferElement<T>

In Unity’s ECS, collections under components are not allowed (yet). The solution for now is to use DynamicBuffer. It’s like a component that you can add to your entities but it’s a list. However, it only accepts value types that implements the IBufferElementData interface. There are times when I just want a list of entities or vectors, but Entity and float3 does not implement IBufferElementData. It’s kind of a pain to create wrapper structs for each type. So I created BufferElement<T>:

[InternalBufferCapacity(10)] public readonly struct BufferElement<T> : IBufferElementData where T : struct { public readonly T value; public BufferElement(T value) { this.value = value; } } // Usage // Say we need a list of Entity targets Entity entity = this.EntityManager.CreateEntity(typeof(BufferElement<Entity>)); DynamicBuffer<BufferElement<Entity>> targets = this.EntityManager.GetBuffer<BufferElement<Entity>>(entity); Entity[] targets = GetTargets(); for(int i = 0; i < targets.Length; ++i) { targets.Add(new BufferElement<Entity>(targets[i])); }

CollectedCommandsSystem

Unity’s ECS was released when our game Academia is already too big with heavy OOP usage. It’s unreasonable to rewrite it to pure ECS. So we’re using a lot of the hybrid interface.

In MonoBehaviour world, you can get access to an EntityManager and create entities with it. One problem we encountered is that too much usage of EntityManager is slow. It has something to do with changing ECS chunk data for each entity/component alteration calls like AddComponentData() or SetComponentData(). The recommendation is to use an EntityCommandBuffer to temporarily pool the commands then execute them in one go so that chunk changes are minimized.

I wanted something that I could use in any of my MonoBehaviours then automatically flush the commands whenever ECS systems kick in. I made CollectedCommandsSystem:

public class CollectedCommandsSystem : ComponentSystem { private EntityCommandBuffer? pendingBuffer; protected override void OnUpdate() { if (this.pendingBuffer != null) { this.pendingBuffer.Value.Playback(this.EntityManager); this.pendingBuffer.Value.Dispose(); this.pendingBuffer = null; } } public EntityCommandBuffer Buffer { get { if (this.pendingBuffer == null) { this.pendingBuffer = new EntityCommandBuffer(Allocator.TempJob); } return this.pendingBuffer.Value; } } } // Usage // Say in a MonoBehaviour private CollectedCommandsSystem collectedCommands; private void Awake() { this.collectedCommands = World.Active.GetOrCreateSystem<CollectedCommandsSystem>(); } private void Update() { // Then somewhere along the way, use the buffer to alter ECS data EntityCommandBuffer buffer = this.collectedCommands.Buffer; Entity entity = buffer.CreateEntity(); buffer.AddComponentData(entity, new Whatever()); }

SharedComponentQuery<T>

When I was creating our custom rendering system, one of the things that I constantly have to deal with are shared components. While working with them, I found that I could make something such that I don’t have to repeat the same code every time I need to handle them. This is what I came up:

public class SharedComponentQuery<T> where T : struct, ISharedComponentData { private readonly ComponentSystemBase system; private readonly EntityManager entityManager; [ReadOnly] private ArchetypeChunkSharedComponentType<T> sharedComponentType; private readonly List<T> sharedComponents = new List<T>(); private readonly List<int> indices = new List<int>(); public SharedComponentQuery(ComponentSystemBase system, EntityManager entityManager) { this.system = system; this.entityManager = entityManager; } public void Update() { this.sharedComponentType = this.system.GetArchetypeChunkSharedComponentType<T>(); this.sharedComponents.Clear(); this.indices.Clear(); this.entityManager.GetAllUniqueSharedComponentData(this.sharedComponents, this.indices); } public T GetSharedComponent(ref ArchetypeChunk chunk) { int sharedComponentIndex = chunk.GetSharedComponentIndex(this.sharedComponentType); int uniqueIndex = this.indices.IndexOf(sharedComponentIndex); Assertion.Assert(uniqueIndex >= 0); return this.sharedComponents[uniqueIndex]; } public IReadOnlyList<T> SharedComponents { get { return this.sharedComponents; } } public IReadOnlyList<int> Indices { get { return this.indices; } } }

It’s a utility class where all of my common code regarding shared components are placed. Here’s a sample usage:

// Say you have a SharedComponent named RenderConfig class RenderConfigSystem : ComponentSystem { private SharedComponentQuery<RenderConfig> query; protected override void OnCreate() { // Prepare the query query = new SharedComponentQuery<RenderConfig>(this, this.EntityManager); } protected override void OnUpdate() { this.query.Update(); // Traverse through all RenderConfig IReadOnlyList<RenderConfig> configs = this.query.SharedComponents; // Start from 1 because it element at 0 is an auto generated instance for(int i = 1; i < configs.Count; ++i) { RenderConfig config = configs[i]; // Do something with config here } } // Another usage is if you want to get the shared component of a chunk (there's only one per chunk) private void Process(ArchetypeChunk chunk) { RenderConfig config = this.query.GetSharedComponent(ref chunk); } }

That’s all for now. 🙂