Search Unity

How to reuse data(definitions) across components?

Discussion in 'Entity Component System' started by TheSmokingGnu, Jan 26, 2020.

  1. TheSmokingGnu

    TheSmokingGnu

    Joined:
    May 1, 2017
    Posts:
    22
    I am trying to implement ability system, and I came up with a inheritance-like system to represent them. Of course, this is pure ECS,and abilities are data-only components. Still, all abilities have the "base" part:

    Code (CSharp):
    1. public struct AbilityData
    2. {
    3.     public float Cooldown;
    4.  
    5.     public bool IsUnlocked;
    6.     public float CooldownEndTime;
    7. }
    and unique part, usually settings/config:

    Code (CSharp):
    1. public struct HealAbility : IComponentData
    2. {
    3.     public int HealAmount;
    4.     public AbilityData AbilityData;
    5. }
    6.  
    7. public struct ArrowAbility : IComponentData
    8. {
    9.     public int Damage;
    10.     public AbilityData AbilityData;
    11. }
    12.  
    13.  
    The main problem with this is that editor does not display the nested struct's properties, even if it implements IComponentData: upload_2020-1-26_20-9-41.png
    How do you solve such problem?

    P.S Initially I planned to have a separate entity for each ability, this way the AbilityData - the base part - would be a first-level component. But It is not viable anymore, since it introduces problems if there is more than one ability-user in the game.
     
  2. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    The crux of the matter is that the Editor unity is using to show component data in the inspector is still quiet early. It doesn’t have support for serialized properties, property drawers, or the like. So currently, there may be no clean way to do what you’d like. But one might potentially expect this functionality down the line. :) :( :)
     
  3. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    49
    what about AbilityData as a IComponentData?
     
  4. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    If you want to draw your own stuff in entity debugger right now it's not that easy since everything is still internal. You will have to use reflections for it and account that any new entity version might break it.
    There is some work done already, have a look at this thread.
     
    TheSmokingGnu and PublicEnumE like this.
  5. TheSmokingGnu

    TheSmokingGnu

    Joined:
    May 1, 2017
    Posts:
    22
    Turns out I had to make my struct [Serializable], as someone mentioned in that post. Now working as expected:
    upload_2020-1-26_22-59-59.png

    Thanks for the link!
     
  6. TheSmokingGnu

    TheSmokingGnu

    Joined:
    May 1, 2017
    Posts:
    22
    However, I stumbled into an obvious problem in my design - I cannot operate on that base data in the generic way. I need to work with the abilities through this base interface: when doing the UI for them, I display only the base data. With my approach, I would need to manually extract the base part for each of my abilities, like this:

    Code (CSharp):
    1. switch(abilityType)
    2. {
    3. case Heal:
    4.     entity.GetHealComponent.BaseData;
    5. case Arrow:
    6.     entity.GetArrowComponent.BaseData;
    7.  
    8. }
    which is awful.
    I am now back to my earlier Idea: separate entity for each ability:

    Entity {
    AbilityData, HealAbilityData
    };

    But now I need to designate who is the owner of this ability... So I need to add an ownerId key field to each entity, database style:
    AbilityData {
    // data
    Entity Owner;
    }

    Which makes me write a custom entity converter, to spawn the ability entities on my own and set their owner field.
    Tedious a little, but at least robust. I guess this is just the case that lends itself well for inheritance.
     
  7. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Your solution now works like foreign key in DB. It could be a hassle for example when destroying entity and you forgot about its other part. In my game I did an equivalent of making AbilityData an IComponentData. Then you just always making sure to add both AbilityData and _AbilityData together. You can ensure that with maybe this kind of generic ComponentType[] generator.

    Code (CSharp):
    1.     public interface IAbility : IComponentData
    2.     {
    3.     }
    4.  
    5.     public struct HealAbilityData : IAbility
    6.     {
    7.     }
    8.  
    9.     public static class AbilityArchetypeManager<T> where T : IAbility
    10.     {
    11.         struct AbilityData : IComponentData
    12.         {
    13.         }
    14.  
    15.         public static readonly ComponentType[] Types = new[]
    16.         {
    17.             ComponentType.ReadOnly<AbilityData>(),
    18.             ComponentType.ReadOnly<T>()
    19.         };
    20.     }
    21.  
    Then you use AbilityArchetypeManager<HealAbilityData>.Types in your GetEntityQuery or as a part of EntityArchetype when you make the entity.

    To make a "base archetype" with a lot of types, I combine multiple ComponentType[] with an iterator. Later when you want to add more types to the base they will then be propagated to the entity's creation. (I often need to add more tag components to subscribe to later added system, adding more function)

    Code (CSharp):
    1.         public static ComponentType[] ArchetypeX = ArchetypeXIter.ToArray();
    2.         public static ComponentType[] ArchetypeY = ArchetypeXIter.ToArray();
    3.  
    4.         static ComponentType[] baseComponents => new ComponentType[]
    5.         {
    6.             ComponentType.ReadOnly<A>(),
    7.             ComponentType.ReadOnly<B>(),
    8.             ComponentType.ReadOnly<C>()
    9.         };
    10.    
    11.         static ComponentType[] archetypeX => new ComponentType[]
    12.         {
    13.             ComponentType.ReadOnly<X>(),
    14.         };
    15.  
    16.         static IEnumerable<ComponentType> ArchetypeXIter {
    17.             get
    18.             {
    19.                 foreach (var t in baseComponents) yield return t;
    20.                 foreach (var t in archetypeX) yield return t;
    21.             }
    22.         }
    23.  
    24.         static ComponentType[] archetypeY => new ComponentType[]
    25.         {
    26.             ComponentType.ReadOnly<Y>(),
    27.             ComponentType.ReadOnly<D>()
    28.         };
    29.    
    30.         static IEnumerable<ComponentType> ArchetypeYIter {
    31.             get
    32.             {
    33.                 foreach (var t in baseComponents) yield return t;
    34.                 foreach (var t in archetypeY) yield return t;
    35.             }
    36.         }
    37.  
    In place where ComponentType[] API is not available (e.g. ForEach, and you can't afford managed garbage here) you still need to specify the base part and the specialized part separately. But I think that is great since you can choose to work on only the base part on the same entity. You can also transform the specialized part based on base part and vice versa easily since it is on the same entity, you can do it in Entities.ForEach
     
    Last edited: Jan 27, 2020