Inventory System Using ECS?

I have tried looking this up, but I haven’t really found anything up-to-date/beneficial from the forums on this topic. I just started researching into ECS Today. I have watched quite a few videos, and I really enjoy what I am seeing.

Now keep in mind, I am not planning on making a game anytime soon, but I would like to know some more about ECS so I can create a ECS mindset for the future.

One good challenge I thought of was to create an Inventory System using ECS. I feel like this would allow me to get an understanding of the Unity ECS system.

My current issue is how would I implement inheritance using ECS? I know I can’t use inheritance in ECS, but how do you implement for example an array of items for an inventory using ECS?

Other things, how do I handle using multiple types of items for example, armor items, weapon items, consumable items. etc…

Main question:

  • How do I create an array inside of an IComponentData (Inventory) struct?
  • How do I handle multiple types of an object (I might be thinking of this the wrong way so I might need some explanation on it)
2 Likes

Dynamic Buffers are used to store multiple components of the same type and attach it to an entity.

I wouldn’t think of it as multiple types of an object, but rather a collection of items with certain components. A component dictates the usage of an item.

A simple inventory can consist of a dynamic buffer of

    [System.Serializable]
    public struct ItemElementData : IBufferElementData
    {
        public Entity itemPrototype;
        public int count;
    }

Each entity in this ItemElementData can have whatever components are needed to make that item unique.

If you want to have an item that you can eat and drink, then you could add a struct Drink : IComponentData { public float value; } and a struct Eat : IComponentData { public float value; } that describes the nutritional value of an item.

2 Likes

Thank you, this cleared up a lot for me. I didn’t know I could store the Entity class in a I(*)Data type, which is where most of my confusion was coming from. Thank you again!

This is actually a commonish question. I think it comes down to ECS doesn’t really match the idea of an Inventory well.

There is really not much work that needs to be computed on an inventory.
As for how to implement it, I can think of half a dozen ways but it also depends if your items are predefined or randomly generated. Here are 3 extremely different solutions.

  1. as @desertGhost mentioned, simply making your items Entities and simply storing a reference to the item entity is probably the common/basic way of doing this.
  • Works for both predefined and randomly generated items.
  • As your item type increases, often requires really messy jobs with lots of ComponentDataFromEntity
  1. don’t use entities. inventories don’t fit that well with ECS so just handle it outside and export into world. Give items a unique ID and just store this ID in a buffer. Items are classes, use inheritance like a regular inventory system.
  • Usually inventory computation is minimal but any actual access to item would have to be done main thread. A lot of access is main thread work anyway though, like displaying on UI.
  • You don’t usually need access to actual item stats in a job. e.g. if an item gives + 5 physical damage, on equip you would cache this in your player stat system. Don’t need to access actual item when doing an actual damage calculation, just the player stats.
  • might be a bit of a gross idea for a lot of people.
  • could even use an existing Inventory library and just write a simple bridge to use it
  1. Use inheritance. You can actually use inheritance in ECS for example physics system has a base Collider struct and then BoxCollders, CapsulteColliders etc.
  • Requires a lot of low level unsafe code.
  • Can easily break if you aren’t careful in your memory mapping
  • Can avoid a lot of ComponentDataFromEntity

If this is just a learning experience for ECS I’d probably find with another topic that’ll help you actually learn ECS properly.

Otherwise if want to actually implement an inventory system I’d probably give 1. a shot.
If you have an existing application which you are converting across or are already very familiar with an inventory system on the asset store that you want to use maybe look at 2.
Otherwise if you are experience with ECS and want a challenge and experience writing low level code, you could try and implement 3.

tldr: use whatever tool for the job that makes the most sense to you.

2 Likes

@tertle
So having a pure ECS game just isn’t a viable thing to do? I should figure out what things I should handle with ECS, and other things that I should handle with legacy unity? Would it be better to just use the burst compiler then, then just using straight ECS?

If not, how do I figure out what should be implemented with ECS, and what shouldn’t be. At the moment, it’s hard for me to distinguish the two.

You can totally make a pure ECS game. Solution 1 and 3 would both be pure ECS for this problem and I recommended doing 1 (though I personally intend to try tackle this problem with 3. at some point just to see how it works. What I posted is more of a theoretical idea in my head than an actual solution atm).

My point is just that there are alternatives depending on your circumstances and you shouldn’t force a solution onto a problem if it doesn’t make sense.

A great example of a slightly alternate solution is Unity Physics.

  • It copies all data off the entities to the Physics world^.
  • Simulates a frame.
  • Then it exports it back out from the physics world back to the Entity world.

^world is not an entity world, just same name is used.

@tertle Thanks for the reply, I think I will try number 1 since I am newer. Then, post the github link here in this thread to see if I made any horrible design choices, so I can learn from them.

As far as number 3 goes, how do you know when it’s right to use inheritance, over pure ecs. I feel like this may be something very easy to abuse, if I am left to just go in and do it with my oop mindset.

It’s still pure ECS but it’s not something you should concern yourself with unless you’re very familiar with low level code and have a problem that really requires it. I just thought about applying it to an Inventory system not long ago which is the only reason I mentioned it. I’m not certain it’ll actually work.

Outside of Unity Physics and a Tween library I wrote, I have not seen anyone use an approach like that. Not to say no one has done something similar and just has never mentioned it, but it is most definitely not part of the normal ECS workflow.

1 Like

Sorry to revive this thread, but I was curious whether solution 3 is a good approach?
I’m currently also trying to build an inventory system in ECS, where different items might have different properties.

Solution 1 would result in a lot of ComponentDataFromEntity usage (I expect quite a lot of items, with a lot of different properties). Most items would belong to categories and thus each item entity contains a Category component. Simply splitting items based on category would already use a ComponentDataFromEntity.

I currently have a working version for Solution 2, but filtering on inventory data, running a system whenever contents of an inventory change (e.g. to update visuals), requires main thread code that seems to be quite ugly.

Solution 3 sounds really nice, but I haven’t been able to find much information on this approach. @tertle Did you give this a try?

If someone else has any success (or fail) story on one of these solutions, I’m happy to hear them! I feel like an inventory is a great example for OOP, and I cannot yet wrap my head around implementing it properly in ECS. Let me know if you need to know anything more (or if I should have created a new forum post and linked back to this one?).

1 Like

Thats why im here. Well im using a java based ecs for my game server and unitys ecs for the client. I actually went for the pure ecs approach… but then the persistence came. Well it actually works, totally possible. But remember one thing, relations become very, very complex when everything is an entity. And this is awfull for persisting our ecs entities. Because when we load them, or persist/update them… we basically need to fetch all relations between those entities. Thats possible, but can be avoided for unimportant relations.

Not everything should be an entity. Not even in pure ecs. Well everything could be an entity, but it shouldnt. If you take pure ecs very, very serious… then even a single attribute could become its own entity. But thats too much. So when exactly should an entity be an entity. When its independet. When it does not depend on any other entities, when its self substaining, when it has its own lifecycle.

In my case inventory items do not have its own lifecycle. They do depend on other entities. They are actually just data holders and looking like this Item{ amount, stackable, category, wearable, slot }. They should never exists without an inventory, so they shouldnt be entities. Thats why “inlined” them. To provide enough flexibility, i created some sort of… well “Entity”, that simply stores a couple of components. Simplified it looks like this.

// Just a container for objects
public class Entity{

   public List<Component> components;

   // Add, Get, Contains, Remove, Iterate
}

// Belongs into our Entity Container object
public class Item{
  bool stackable;
  short amount;
  string slot;
}

// Inventory for an ecs entity
public class Inventory : IComponentData{
  public List<Entity> items;
}

This way our ECS entity with an inventory points towards EC-Entities. Those EC-Entities are just containers of components and pretty flexible. We can add and remove components from those like we would do it with an ECS-Entity. Its actually inspired by unitys monobehaviours. But much, much more simpler.

1 Like

Ideally this is true, but in order for our data to be compatible with jobs/burst we can’t use typical oop solutions. It’s not even about performance, but about working smoothly with Unity’s ecs. When you start introducing reference types and inheritance into it things tend to get annoying fast in my experience.

In my game my items are entities, but the items hold a reference back to the thing holding them - but not the other way around. If the player needs to see all the items it holds, a system will do a ForEach over the “InInventory” component and filter items held by the player.

Not super intuitive but it keeps the items self contained and plays well with Unity’s ecs.

2 Likes

Totally understandable ^^ My solution doesnt work with burst/jobs and isnt performing that good. But if we dont target max performance, its a valid approach. The intention is to minimize relations between entities, especially for entities that do not have any real lifecycle or those that cant exist without another entity.

It also really depends on what you plan to do with the items. I do not need to iterate over my items. They are just used to buff the players stuff or probably… probably to execute some sort of event. So i do not care about performance here. Its basically event based and does not have any effect. But to remove such loose relations actually helps the database layer ( if theres one ). Less relations between entities means less tables and easier mapping code.

I was playing around with MongoDB for a bit and persisted entities as documents in an collection. The problem i ran into was that i loaded a chunk ( also an entity ) from the collection and due to the high amount of relations i had, i was forced to run another query to load up the entities inside that chunk. Then another one for the inventorys of those entities and then another one for possible relations between items and other stuff. You see where this is going ^^ Its just a tradeoff.

Does the performance matter for items ? Go pure. Do you need to query those items, iterate over them ? Go pure. Do you want to have less relations between entities ? Go with structs or some sort of selfcoded flexible EC.

2 Likes

Interesting thread here.

I’m new to unity but have done a lot of research into the Unity ECS system. Here is my take on how this may or may not be used in an inventory system:

The motto or tagline is “Performance by default”. From my understanding, the ECS / job system is more about optimizing taxing calculations and the memory management around those calculations.

To me, an inventory system, at least in most games I’ve ever played, isn’t really taxing.

I’m not really dragging thousands or hundreds of thousands of objects into / out of my inventory per second or frame.

But the usage of ECS on the “really taxing stuff” (hit calcs, collisions, buffs, etc…), I feel, would benefit an inventory system made with Mono behaviors, game objects and inheritance.

By building an inventory system that uses monobehaviors, gameobjects and inheritance you are essentially giving up the ability to easily get any performance benefits from ECS in any system/job that needs to reference or use items. This is what I was alluding to in my post above.

If your items are reference types for example then any component or system that needs to reference an item is no longer burst/job compatible, unless you start getting into unsafe code, managing your own pointers, or essentially creating your own item id database (what ECS is trying to do for you in the first place) which is a whole other can of worms.

There are a lot of concerns surrounding items in games that will have different needs. Embrace the paradigm of transforming data for the context is my advice.

If you aren’t reasoning about it all the way back to the database it’s not a complete picture.

Also if you don’t have a notion of static and instance data it’s not complete.

Server side data handling is where things can vary the most. Client side you can standardize more. Standardizing models end to end is a challenge.

Value types as your top level models for static data can be problematic. Those models invariably end up having collections, which might be other reference types. Nosql databases don’t play nice with custom value types either. And items are not something you are iterating over a huge amount constantly either. Big picture it’s mostly lookups by key.

What does work is just make sure all of your end data is value types. That ensures it can be flattened into ECS components, buffers, native collections, and network messages.

Also while it might seem baking static data to blobs is a good idea, it’s not that simple. You are almost always accessing static data via a key lookup like by sku, not iterating over it. So what you really want is a single hashmap of static data, maybe a couple of secondary hashmap indexes. It’s simpler and more practical to just create that on startup and then disable all safety checks on the collection. And then pass around a simple abstraction over it that has read only access.

So for our game what works well is static data models are the same on client and server. But client side we have a small handful of proxy structs. So static data is entered in SO’s that contain lists of the models. And we bake that out to nativehashmap’s using the proxy structs. Here though things can vary a lot by game, items just vary so much depending on the game.

You guys are forgetting that ECS is basically SQL, you don’t list what the owner contain, but tell whom it is owned by.

All you need to set up a inventory system in ECS is setting a “ItemOwner” SharedComponentData to all items and there you go. You can just query for all entities sharing the same ItemOwner SharedComponentData to get the whole inventory of said ItemOwner, you can even nest inventories with it.

Stacking items is one hell of a headache, since they are entities and they all can have different componenets and whatnot, but besides that, everything easily falls into place when doing like this.

You say you don’t list what the owner contains then proceed to implement that exact paradigm…

Wouldn’t a shared component data with different value per owner bring a lot of chunk fragmentation ?

Personally I would avoid shared component. Only use it like they use it in boids example. Updating the shared component per entity is super slow. Even with less than 10 k entities. Ok if you need to do just one frame update.

I tried using the SCD to divide moving entities on to different chunks every frame. The boids system uses the scd quite cleverly during conversion, but what if you need to update the value after spawn? Super slow.

What you mean? The Item stores who owns it, the owner don’t even need to be an entity, it can be anything, it’s just the value of the SharedComponenetData.

Unfortunately yes, would be nice if we could get custom chunk sizes one day

Edit:

Why would an Item change inventory every frame, why would all items do that every frame? You only change the “Owner” SCD when the item changes hands, which shouldn’t be very often, even if money is made into an item.

1 Like