Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Inventory System Using ECS?

Discussion in 'Entity Component System' started by Deleted User, Nov 10, 2019.

  1. Deleted User

    Deleted User

    Guest

    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)
     
    lndcobra and bjarkeck like this.
  2. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    258
    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

    Code (CSharp):
    1.  
    2.     [System.Serializable]
    3.     public struct ItemElementData : IBufferElementData
    4.     {
    5.         public Entity itemPrototype;
    6.         public int count;
    7.     }
    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.
     
    lndcobra and bjarkeck like this.
  3. Deleted User

    Deleted User

    Guest

    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!
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    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

    2. 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

    3. 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.
     
    Last edited: Nov 10, 2019
    lndcobra and bjarkeck like this.
  5. Deleted User

    Deleted User

    Guest

    @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.
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    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.
    https://exceptionnotfound.net/the-golden-hammer-anti-pattern-primers/
    https://sourcemaking.com/antipatterns/golden-hammer

    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.
     
  7. Deleted User

    Deleted User

    Guest

    @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.
     
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    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.
     
    martinusprim likes this.
  9. manpower13

    manpower13

    Joined:
    Dec 22, 2013
    Posts:
    140
    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?).
     
    genaray likes this.
  10. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    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.

    Code (CSharp):
    1.  
    2.  
    3. // Just a container for objects
    4. public class Entity{
    5.  
    6.    public List<Component> components;
    7.  
    8.    // Add, Get, Contains, Remove, Iterate
    9. }
    10.  
    11. // Belongs into our Entity Container object
    12. public class Item{
    13.   bool stackable;
    14.   short amount;
    15.   string slot;
    16. }
    17.  
    18. // Inventory for an ecs entity
    19. public class Inventory : IComponentData{
    20.   public List<Entity> items;
    21. }
    22.  
    23.  
    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.
     
    manpower13 likes this.
  11. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    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.
     
    manpower13 and genaray like this.
  12. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    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.
     
    manpower13 and Sarkahn like this.
  13. JJWilliams

    JJWilliams

    Joined:
    May 1, 2017
    Posts:
    3
    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.
     
  14. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    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.
     
  15. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    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.
     
  16. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    824
    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.
     
  17. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    You say you don't list what the owner contains then proceed to implement that exact paradigm...
     
  18. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    Wouldn't a shared component data with different value per owner bring a lot of chunk fragmentation ?
     
  19. mikaelK

    mikaelK

    Joined:
    Oct 2, 2013
    Posts:
    281
    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.
     
  20. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    824
    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.
     
    Last edited: Jun 19, 2021
    JakeTBear likes this.
  21. mailfromthewilds

    mailfromthewilds

    Joined:
    Jan 31, 2020
    Posts:
    215
    why? monob has better interaction with at least UI and maybe file saving

    dots is best for stuff that happens every frame

    so not inventory
     
  22. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    824
    Not really, the fact that 99% of the data lies in structures enables one to do some crazy stuff, I've been abusing it with Reflection to automagically save entire worlds almost intantly. My save system has been using it for some months now and am still to find anything catastrophic about it. I still need to make some method to handle component version differences (add a int to a component that didn't have it previously for instance and now I can't automagically deserialize it), but besides ensuring forward compatibility on the saves, it's been pretty good. The other less common data structures are saved through calling interface methods;

    Maybe there is some uncontious need to ensuring everything you do in ECS is burst compatible and must always be using to it's fullest extent or not using it at all?
    Sure even if plenty of my inventory systems are not burstable nor paralyzable, my mono behaviour inventory alternative wouldn't be either
    Heck, the fact that my inventory is done in ECS means other ECS features can effortlessly interact with it (this includes the previously mentioned off topic saving system).Even if there is some hypotetical NativeHashmap solution that outperforms it, I wouldn't use it.
     
  23. mikaelK

    mikaelK

    Joined:
    Oct 2, 2013
    Posts:
    281
    I guess it completely depends on the game and how much are you willing to let your performance drop over item system. In a game where there is hundreds-thousands of enemies and each has change to drop items it might get nasty. Also the performance drop happens on the mainthread which is another issue.

    Depending on the complexity it could be better to go with classes or burstable components and not stress the enitity managers main thread performance. Just my observation after using the scd for few weeks.

    If your game is static and nothing special is happening on the screen then just pick the easiest to do. well in this case ecs is overkill anyways. Unless you want the game to be power efficient and work on ancient mobile devices and laptops.

    I have tried pretty much everything from limiting the system to only update when something changes or run the update only after something happened with each opening a new door to different problems in a scene where is spawning and despawning hundreds of entities. For example entity does not exists in some command buffer etc.

    I even opened up several topics for how to optimize these, but no one has been able to give a fix for the issues.
     
    Last edited: Jun 20, 2021
    Guedez and apkdev like this.
  24. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    824
    I guess enemies dropping items would constitute changing inventory parent, yeah, in a situation where 3000~ enemies die per frame you might want a different approach.
    But that's a very specific case, I'd wager (without any evidence) that not even world of warcraft had 180000 enemies die per minute, not even if you add up every single server on it's peak.