I need design advice for a simple scenario. Suppose we have container components called “box”.
public struct Box : IComponentData
{
}
Every box containes “items”. Items have “itemType” and “amount” properties.
public enum ItemType
{
ITEM_A,
ITEM_B,
ITEM_C
//...
}
And finally there are agents that can pick up or put down items in&out the boxes.
public struct PickUpAction : IComponentData
{
public Entity box;
public ItemType itemType;
public int amount;
}
What is the best way of making a relationship between boxes, items, and pick up actions?
First I tried DynamicBuffers.
public struct ListItem : IBufferElementData
{
public ItemType itemType;
public int amount;
}
Since the content of the boxes is not fixed (there can be lots of item types), I don’t think it is good solution. But on the other hand writing a system for pick up jobs is very easy. (By the way, is there a way to run this parallel?)
protected override void OnUpdate()
{
var boxes = GetBufferFromEntity<ListItem>(false);
Entities.ForEach((int entityInQueryIndex, Entity entity, in PickUpAction pickUpAction) =>
{
var boxContent = boxes[pickUpAction.box];
for (int i = 0; i < boxContent.Length; i++)
{
var item = boxContent[i];
if (item.itemType == pickUpAction.itemType && item.amount >= pickUpAction.amount)
{
item.amount -= pickUpAction.amount;
boxContent[i] = item;
break;
}
}
}).Schedule();
}
Then I tried moving items to their own entities. And things started to get complicated…
public struct Item : IComponentData
{
public Entity box;
public ItemType itemType;
public int amount;
}
protected override void OnUpdate()
{
var ecb = ecbSystem.CreateCommandBuffer().AsParallelWriter();
NativeArray<Entity> allItemEntities = GetEntityQuery(ComponentType.ReadOnly<Item>()).ToEntityArray(Allocator.TempJob);
var allItems = GetComponentDataFromEntity<Item>(true);
Entities
.WithDisposeOnCompletion(allItemEntities)
.WithReadOnly(allItemEntities)
.WithDisposeOnCompletion(allItems)
.WithReadOnly(allItems)
.ForEach((int entityInQueryIndex, Entity entity, in PickUpAction pickUpAction) =>
{
for (int i = 0; i < allItemEntities.Length; i++)
{
var item = allItems[allItemEntities[i]];
if (item.box == pickUpAction.box && item.itemType == pickUpAction.itemType && item.amount >= pickUpAction.amount)
{
item.amount -= pickUpAction.amount;
ecb.SetComponent(entityInQueryIndex, allItemEntities[i], item);
}
}
}).ScheduleParallel();
ecbSystem.AddJobHandleForProducer(Dependency);
}
Is this the right way to get the related items?
Since this system uses entity command buffer, the items will not be updated until it finishes running. This brings a problem. Suppose we have one box and an item entity is linked to it. This item entity has 1 amount of ITEM_A. When 10 pickup actions try to get this one item, they will all succeed. Because the amount is not updated and all the agents see there is 1 amount of ITEM_A. How can I solve this issue? Is there a way to check if the data is updated from this or another system?
Here is the source code if you want to play around? Thanks.
Source
using System;
using Unity.Collections;
using Unity.Entities;
public enum ItemType
{
ITEM_A,
ITEM_B,
ITEM_C
//...
}
public struct Box : IComponentData
{
}
public struct Item : IComponentData
{
public Entity box;
public ItemType itemType;
public int amount;
}
public struct ListItem : IBufferElementData
{
public ItemType itemType;
public int amount;
}
public struct PickUpAction : IComponentData
{
public Entity box;
public ItemType itemType;
public int amount;
}
public class PickUpItemSystem : SystemBase
{
private EntityCommandBufferSystem ecbSystem;
private System.Random random = new System.Random();
protected override void OnCreate()
{
ecbSystem = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
EntityManager manager = World.DefaultGameObjectInjectionWorld.EntityManager;
//Test boxes
EntityArchetype boxEntityArchetype = manager.CreateArchetype(typeof(Box));
int boxCount = 1;
NativeArray<Entity> boxEntities = new NativeArray<Entity>(boxCount, Allocator.Temp);
manager.CreateEntity(boxEntityArchetype, boxEntities);
//Test buffer items
//for (int i = 0; i < boxEntities.Length; i++)
//{
// manager.SetName(boxEntities[i], $"Box {i}");
// var buffer = manager.AddBuffer<ListItem>(boxEntities[i]);
// buffer.Add(new ListItem
// {
// itemType = ItemType.ITEM_A,
// amount = 1
// });
// //buffer.Add(new ListItem
// //{
// // itemType = ItemType.ITEM_B,
// // amount = random.Next(100)
// //});
//}
//Test items
EntityArchetype itemEntityArchetype = manager.CreateArchetype(typeof(Item));
int itemCount = 1;
NativeArray<Entity> itemEntities = new NativeArray<Entity>(itemCount, Allocator.Temp);
manager.CreateEntity(itemEntityArchetype, itemEntities);
for (int i = 0; i < itemEntities.Length; i++)
{
manager.SetName(itemEntities[i], $"Item {i}");
manager.SetComponentData(itemEntities[i], new Item
{
box = boxEntities[random.Next(boxCount)],
itemType = ItemType.ITEM_A,
amount = 1
});
}
//Agents
EntityArchetype agentEntityArchetype = manager.CreateArchetype(typeof(PickUpAction));
int agentCount = 10;
NativeArray<Entity> agentEntities = new NativeArray<Entity>(agentCount, Allocator.Temp);
manager.CreateEntity(agentEntityArchetype, agentEntities);
//Test items
for (int i = 0; i < agentEntities.Length; i++)
{
manager.SetName(agentEntities[i], $"Agent {i}");
manager.SetComponentData(agentEntities[i], new PickUpAction
{
box = boxEntities[random.Next(boxCount)],
itemType = ItemType.ITEM_A,
//amount = random.Next(10) + 1
amount = 1
});
}
}
protected override void OnUpdate()
{
//var ecb = ecbSystem.CreateCommandBuffer().AsParallelWriter();
//var boxes = GetBufferFromEntity<ListItem>(false);
//Entities.ForEach((int entityInQueryIndex, Entity entity, in PickUpAction pickUpAction) =>
//{
// var boxContent = boxes[pickUpAction.box];
// for (int i = 0; i < boxContent.Length; i++)
// {
// var item = boxContent[i];
// if (item.itemType == pickUpAction.itemType && item.amount >= pickUpAction.amount)
// {
// item.amount -= pickUpAction.amount;
// boxContent[i] = item;
// ecb.AddComponent<Disabled>(entityInQueryIndex, entity);
// break;
// }
// }
//}).Schedule();
//ecbSystem.AddJobHandleForProducer(Dependency);
var ecb = ecbSystem.CreateCommandBuffer().AsParallelWriter();
NativeArray<Entity> allItemEntities = GetEntityQuery(ComponentType.ReadOnly<Item>()).ToEntityArray(Allocator.TempJob);
var allItems = GetComponentDataFromEntity<Item>(true);
Entities
.WithDisposeOnCompletion(allItemEntities)
.WithReadOnly(allItemEntities)
.WithDisposeOnCompletion(allItems)
.WithReadOnly(allItems)
.ForEach((int entityInQueryIndex, Entity entity, in PickUpAction pickUpAction) =>
{
for (int i = 0; i < allItemEntities.Length; i++)
{
var item = allItems[allItemEntities[i]];
if (item.box == pickUpAction.box && item.itemType == pickUpAction.itemType && item.amount >= pickUpAction.amount)
{
item.amount -= pickUpAction.amount;
ecb.SetComponent(entityInQueryIndex, allItemEntities[i], item);
ecb.AddComponent<Disabled>(entityInQueryIndex, entity);
}
}
}).ScheduleParallel();
ecbSystem.AddJobHandleForProducer(Dependency);
}
}