Search Unity

Multiple components of the same type on an entity

Discussion in 'Entity Component System' started by Cynicat, Jun 11, 2018.

  1. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    Unity's ECS appears to be designed around only having a single component of a certain type, while this is actually pretty good practice, there are times when you actually need multiple. For example, it's pretty standard practice in ECS to add components to work as messages, like damage, collision contacts, etc... to show the advantages of this, let's look at a rpg health system:

    1. Some system wants to deal damage to an entity, so it adds a new DamageComponent(4, DamageType.Fire)
    2. FireResistanceSystem finds all DamageComponents and FireResistanceComponents and lowers damage to reflect this entities fire resistance
    3. ApplyDamageToShieldSystem finds all DamageComponents and ShieldComponents and subtracts damage from shield, removing/lowering damage based on how much was absorbed by sheild.
    3. ApplyDamageSystem finds all DamageComponents and HealthComponents and subtracts damage from health
    4. DeathSystem finds all HealthComponents, if health.value is lower than 0, set it to 0 and add a OnDeathComponent.

    Benefits of all this add and remove pattern is that each system doesn't know about other systems. this makes code very resistant to inter-system bugs and makes it super easy to extend. For example the shield system is totally independent from resistances and health, its one system that adds a whole shield mechanic, requiring no changes to other systems. Extension becomes dead easy.

    Problem: all this requires multiple components of the same type. what happens if a characters gets damaged twice on the same frame? What happens if i want the entity to have multiple shields? answer is nothing at the moment.

    Hacks: there are hacks to get around this, one simple one is storing Lists for each value or storing a list of structs. This is actually pretty clean as workflows go, but in Unity's ECS is a huge pain because you have to manage the lifecycle of every List and also causes a cache miss on every entity (separate lists). another is to store some fixed amount of copies within the component, usually in something like a float4, int4, bool4, etc... while this is actually a pretty simple approach it assumes you only need X amount of components and adds a bunch of extra data to the components if you might need to store many.

    My proposed solution: have something like IMultiComponentData, which supports multiple components per entity. This might require some kind of MultiComponentDataArray as well, and i understand this adds some surface area to the API.

    TLDR: Please allow multiple components per entity! =3
     
    HouinKyouma27 and mkracik like this.
  2. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    There is one more way to do this and IMO sounds inline with ECS, that is create a new multiple entities just for those components. Especially things that does not seems to "belongs" to an entity but more of a message/reactive data goes well with this approach. Player should have health but not "having" damage. But things like poisoned I think sounds more like things that attached to an existing entity.

    Then getting them to take action is easy, just create a system that inject all entities with those message. In the message you could store an Entity who is the receiver of damage. After looping through destroying them all is also more efficient since you can use batched API of DestroyEntity and not having to remove component one by one if you were to attach damage to players.

    EntityCommandBuffer even has a method that makes doing this from the job easy, that is CreateEntity void return -> AddComponent without entity argument. It automatically input the latest CreateEntity issued from EntityCommandBuffer in succession.

    If you want multiple systems to do their job on the damage before clean them up, make a message destroyer system with [UpdateBefore(typeof(Initialization))] for destroying those damage entities. How to destroy them all if you have multiple message types? Add ISharedComponentData to all of them. (In my game I made an extension method on EntityCommandBuffer called .Issue where it add the desired message along with a fixed `ReactiveEntity` ISharedComponentData. The destroyer system then can inject based on that.
     
    Afonso-Lage likes this.
  3. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    Hmmm, this seems like a decent way of doing things, complex but robust. I'll need some time to wrap my head around it but this may work. I like having damage as a component since it makes it a simple check if an entity has pending damage, being a HasComponent call basically. For me i find it more intuitive to add and remove data rather than using entity references, but a lot of that comes from experience with unreliable reference management in other systems i've worked with. Thanks for the suggestion i'll try it out! =3
     
  4. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    What I am interested in is something like a buff or modifier system. Basically a system for making temporary or periodic modifications on other components that can't be combined into one because they run on different timers. Having to make a specialized component for every possible buff/debuff or similar things doesn't really sound optimal. And there is also the possible requirement of wanting multiple of the same effect to "stack" up but still be separate. Creating a new entity for every modification instance also doesn't sound all too ideal.
    I've had the same problem with other ECS implementations and Unity is even more strict about not allowing any managed data on IComponentData so really no kind of Collection to manage the data.

    Has anyone come up with a good solution for this kind of problem yet? If not I'm going to try to "hack" something together myself.
     
    Last edited: Jun 12, 2018
  5. Arakon

    Arakon

    Joined:
    Mar 8, 2014
    Posts:
    23
    I've been thinking about this myself for some similar needs.
    Summary: I used separate entities for effects/buffs, and had systems to collect and aggregate them. Mostly worked off aggregated data.

    With Entitas I had a setup once with separate entities for each effect. An EffectAttributeComponent would have an Attribute enum and value. This allowed for different lifetime-management components to be added on the separate effects. There was an OwnerEntityComponent that referenced the host entity (by a unique ID). Initially I used an Entitas Reactive system to collect these into an "EffectsComponent" that the parent entity had (to allow quick access). Eventually Entitas added an Indexing concept, which I used instead (attribute based, and generated, so easy usage). I could get all effects a given parent ID had, and then loop through them through other systems as needed. It worked pretty well in the end, though it took a bit to get used to.

    For Unity ECS, it's probably not the best solution. less useful with the pure ECS approach since the data layout isn't as good. Not sure if it would be usable in jobification, or if so, if it would be efficient.

    The concept could probably be teased apart more though, and use some event/messaging components, and only process when changes occur. When changes happen, process the entities that changed and consolidate/aggregate them into a component that is attached to the parent.
     
  6. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    I hope we'll see a System event system similar to what Entitas has with Unity as well. Like calls when a component gets added or removed etc.

    Would be nice for things that do something when they attach or release. Like the buff or debuff example I mentioned.

    I now did a basic implementation doing a new Entity for each Modification/Buff etc. but I still don't really like it that way. Also makes it a lot harder to Debug because all the data is on separate Entities.
     
    Cynicat likes this.
  7. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    Yeah the seperate entities solution has the problem that it's super cache inefficient as well, as you need to look up an entity via it's ID every time, causing a cache miss. =/
     
  8. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    It's better to create your abstractions here on the underlying mechanics at play, not the end user effects. Below is a screen of my Effect component with the effect types expanded. Not all of those have been abstracted to their most granular forms. Like for the stat increase/decrease I could have further abstracted out the stat type.

    The main thing is think in terms of mechanics. If you go with something like FireBallComponent you will end up with a lot of duplicate logic and it will just generally start to become a mess.

    My stuff is server side outside of ECS. But I would still use the exact same approach for the abstractions.

    If you have a notion of effect stacking then you probably want to have an EffectStack component that logically contains your Effect components. Like create an effect stack id and then use NativeMultiHashMap for example to hold the effects.

    Dynamic grouping you can't do well with ECS unfortunately. IMO better to just use what is more natural like NativeMultiHashMap then to try and force it and end up with something much more complicated.

    For combat type effects you really don't need to worry that much about performance. It's not like you are dealing with thousands of updates per frame. Your effect tick rates are going to be more on like a one second interval, and they would likely be staggered as well.


    upload_2018-6-12_15-45-24.png
     
    zyc_dc likes this.
  9. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    NativeMultiHashMap actually seems like an almost perfect solution to this problem, but i'm having trouble finding examples for it. The closest i could find is the boids example and i can't make heads or tails of it =/ I really wish unity would actually document these classes already. X<
     
  10. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    You can't have any collections including NativeArray, NativeMultiHashMap etc. or even primitive arrays on IComponentData. Unity does not allow it as I already mentioned above.

    Here is the message you get trying any of it:
    ArgumentException: com.binaryfeast.ECS.ModifyAttribute is an IComponentData, and thus must be blittable (No managed object is allowed on the struct).

    I thought NativeArray must be blittable since it's just a native array abstraction but apparently it's not. Primitive arrays of a blittable type are also supposed to be blittable but Unity still doesn't allow it, maybe they are a bit too overly zealous?
     
    Last edited: Jun 13, 2018
  11. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Try looking at how the transform hierarchy is built and traversed in TransformSystem.cs, that has a good example of how NativeMultiHashMap is used.
     
    zulfajuniadi likes this.
  12. LukePammant

    LukePammant

    Joined:
    Mar 10, 2015
    Posts:
    50
    This is a bit of a necro-thread but I'm curious if you guys have figured out a good way of doing this in an ECS way? I have a scenario just like what you're describing with damage/effects.
     
  13. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Dynamic Buffer