Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Combine components at runtime

Discussion in 'Entity Component System' started by Tony_Max, Feb 6, 2020.

  1. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    I faced a problem with generating entities based on different sets of IComponentData.

    In my project i have units that contains some modules. Each module provides some sets of IComponentData for logic. During the game player decides which modules the unit will consist off.

    So to do it i saw 3 different ways:
    1. Convert gameobject at start, store result entities by enum ModuleType, and when i need to provide IComponentData i will access all components and extract all data. The problem is that i need to know all types of IComponentData attached to entity which is impossible. I looked for some tools to get union of an EntityArchetype, but we have no such a thing. Ok then, let's try to store types inside external collection. And with this way i can also store string[] of types and fill it in editor, but you can't use Type with generic T parameter.
    2. The second way is to combine GameObject prefabs with auto generated authoring component at runtime and then conver it. But how to place all IComponentData from different gameobjects to one entity? I thought about moving authoring MonoBehaviour from sub gameobject to root gameobject but again u can't do this and also you can't access to generated mono type.
    3. The thrird way is to have manually instractions for all kind of modules. It's super dirty for me, but i see no other way.
    So i need an advice of how to implement it.
     
    Last edited: Feb 6, 2020
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,223
  3. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    Thank you so much. Will see how it works.
     
  4. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    I have implement a solution to save components sets to ScriptableObject using custom editor. But having just IComponentData[] i can't add components to entity with values, only components with default values. When i trying to pass item from IComponentData[] to EntityManager.AddComponent/.SetComponent i get error which says:
    ArgumentException: Unknown Type:`Unity.Entities.IComponentData` All ComponentType must be known at compile time.
    . All i can is just add very component by using ComponentType structure. Is there a way to do that kind of stuff?
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,223
  6. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    Here is my scriptable object that holds components as objects and have a method to extract IComponentData[]
    Code (CSharp):
    1. public class ComponentPreset : ScriptableObject
    2.     {
    3.         [HideInInspector]
    4.         [SerializeReference]
    5.         public object[] components;
    6.  
    7.         public IComponentData[] GetIComponentDataSet()
    8.         {
    9.             var iComponentDataArr = new IComponentData[components.Length];
    10.             System.Array.Copy(components, iComponentDataArr, components.Length);
    11.             return iComponentDataArr;
    12.         }
    13.     }
    And here i use my scriptable object to get component set and use it in Convert method
    Code (CSharp):
    1. public class Unit : Monobehaviour, IConverGameObjectToEntity
    2. {
    3.    [SerializedField]
    4.    private ComponentPreset _componentPreset;
    5.  
    6.    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    7.    {
    8.       foreach(var component in _componentPreset.GetIComponentDataSet())
    9.          dstManager.AddComponent(entity, component);
    10.    }
    11. }
     
    Last edited: Jun 18, 2020
  7. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    I use a simillar aproach in the MGM Skill system.
    The trick is to deleagete the addition of the component to the component itself.
    That way the component know what is is own type and therfore can use the AddComponentData for mthe dstManager.

    Code (CSharp):
    1.  
    2.  
    3.     public interface ISelfConvertingComponentData: IComponentData
    4.     {
    5.         void Convert(Entity entity, EntityManager dstManager,GameObjectConversionSystem conversionSystem);
    6.     }
    7.  
    8.  
    9.  
    Code (CSharp):
    1. public class Unit : Monobehaviour, IConverGameObjectToEntity
    2. {
    3.  
    4.    [SerializedField]
    5.    private ComponentPreset _componentPreset;
    6.  
    7.    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    8.    {
    9.       foreach(ISelfConvertingComponentData component in _componentPreset.GetIComponentDataSet())
    10.          dstManager.Convert(entity, dstManager,conversionSystem);
    11.    }
    12.  
    13. }
    Code (CSharp):
    1.  
    2. public struct SomeComponent : ISelfConvertingComponentData
    3. {
    4.  
    5.    [SerializedField]
    6.    private ComponentPreset _componentPreset;
    7.  
    8.    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    9.    {
    10.         dstManager.AddComponentData(entity, this);
    11.    }
    12.  
    13. }
     
    Tony_Max likes this.
  8. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    Oh, i understand, it should work. But it crashes a bit ECS principles in which component contains only data and never logic.
     
    Last edited: Jun 18, 2020
  9. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    I need to assign all the prefab's components to my existing entity, but I'm not sure how to achieve it either.

    I want to assign a ghost prefab's data to my character on the server and assign character prefab to the ghost prefab on the client
     
  10. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    I agree but it's hardly logic to just do the add component data IMO.
    Another solution would be to use reflection to "create" the invoke method for the detected type of the component.
    Probably not as performant though.
     
  11. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Not sure of why you need it but maybe look into :

    EntityManager.GetChunk(Entity).Archetype.GetComponentTypes(Allocator)

    https://docs.unity3d.com/Packages/c...EntityManager_GetChunk_Unity_Entities_Entity_
    https://docs.unity3d.com/Packages/com.unity.entities@0.11/api/Unity.Entities.ArchetypeChunk.html
    https://docs.unity3d.com/Packages/com.unity.entities@0.11/api/Unity.Entities.EntityArchetype.html
    https://docs.unity3d.com/Packages/c..._EntityArchetype_GetComponentTypes_Allocator_

    This should give you the list of compnent on the entity and a good (?) starting point.

    EDIT : Even beter -> EntitManager.GetComponentTypes(Entity, Allocator)
    https://docs.unity3d.com/Packages/com.unity.entities@0.11/api/Unity.Entities.EntityManager.html#Unity_Entities_EntityManager_GetComponentTypes_Unity_Entities_Entity_Allocator_

     
    Last edited: Jun 18, 2020
  12. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    Can we get a component data value having NativeArray<ComponentType> ?
     
  13. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    I had similar problem with my item system. My items consist of structures like your modules. Basically those structures are lists of IComponentData and ISharedComponentData. In your case you can do the same and create UnitManager which would store spawned combinations of modules in dictionary.
    I don't really understand this part. Why would you want to extract all components from entity?
     
  14. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Last edited: Jun 18, 2020
  15. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    I dont't know all possible combinations
     
  16. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    Just add the combinations that player wants to the dictionary to spawn them faster next time.
     
  17. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    problem not in performance. i need to add combinations of SETS of components on the one entity. To do that i need have lists of components. DOTS give no way except manually write code for every set individually. All that because when u do AddComponent u need to assign T explicitly
     
    Last edited: Jun 18, 2020
  18. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    the problem is - you can't add this self convert function to DOTS structs like LocalToWorld without rewriting it's source code every time DOTS do update
     
  19. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    Where exactly is the problem then? I understand that player says he wants module A, B, C in the Unit, then you spawn Entity and add components from A then from B and from C.
     
  20. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    For example:
    module A requires components: LocalToWorld, MoveSpeed, Acceleration
    module B requires components: Armor, Health
    module C requires components: Attack, AttackRange, AttackSpeed
    And all of module contains data (not just the types themselves)

    And how you will add all of this on the same entity? I mean withou big boilerplate code.
     
  21. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    I would do it like this:
    1. Create class that stores all modules setup for given unit: UnitModuleStorage
    2. On entity spawning UnitModuleStorage gets all component types from modules and spawns unit entity with given archetype
    3. UnitModuleStorage has method AddComponentsToEntity and uses SetComponentData (or SetComponent in case of entityCommandBuffer which is better) on just spawned entity to transfer data from your setup.
    Maybe there's something I didn't get and you can't do it like this, then explain why and I will try to help.
     
  22. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    You should be able to add an extension method :
    Code (CSharp):
    1.     public static class LocalToWorldExtensions
    2.      {
    3.         public static void Convert(this LocalToWorld localToWorld, Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    4.         {
    5.             dstManager.AddComponentData(entity, localToWorld);
    6.         }
    7.     }
     
  23. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    Yes it's the only way today u can do it without any extensions. But if you have various of modules with different sets of components and with different values of components them u will have waterfall-like ugly code (imho). U can get some reduction using inheritance but still :)
    Thet's why i trying to find nice solution to just store sets of components in some assets.
     
  24. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    yes but how to say ISelfConverting interface that this new extension method is his implementation?
     
  25. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    I am using this method in slightly modified way and it's quite convenient. Code is not ugly and is very short. What do you mean by waterfall-like?
     
  26. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    Can u show some of your code? May be i could see some good practics and use it in my project. When i say waterfall-like code i mean code for example with tones of if statements or with 20 lines of EntityManager.AddComponent(...)
     
  27. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    @Tony_Max
    I did this example now and it may have some flaws but point out anything that seems weird to you.
    First thing is the Module class:
    Code (CSharp):
    1. public interface IUnitModule
    2. {
    3.     ComponentType[] GetComponentTypes();
    4.     void AddDataToEntity(Entity e, EntityManager entityManager);
    5. }
    6.  
    7.  
    8. public class UnitModule : IUnitModule
    9. {
    10.     public Dictionary<Type,IComponentWrapper> componentDatas = new  Dictionary<Type,IComponentWrapper>();
    11.  
    12.     public ComponentType[] GetComponentTypes()
    13.     {
    14.         return componentDatas.Select(data => (ComponentType)data.Key).ToArray();
    15.     }
    16.  
    17.     public void AddDataToEntity(Entity e, EntityManager entityManager)
    18.     {
    19.         componentDatas.ForEach(data => data.Value.AddComponentToEntity(e, entityManager));
    20.     }
    21.  
    22. }
    23.  
    The first thing you can see there is the dictionary of Type and IComponentWrapper. Type is there because Unity doesn't allow you to get type from IComponentData and set it as archetype, you also can't use ComponentType as Key because Unity is weird about it and it doesn't work (I didn't check exactly why but it doesn't work)
    Next thing is IComponentWrapper. It's interface that magically will let you add components to entity from list. Don't ask me why, I knew it few months ago but I don't do now unfortunately.
    Next thing is ComponentWrapper:
    Code (CSharp):
    1.  
    2. public interface IComponentWrapper
    3. {
    4.     void AddComponentToEntity(Entity e, EntityManager entityManager);
    5. }
    6.  
    7. public class ComponentWrapper<T> : IComponentWrapper where T : struct,IComponentData
    8. {
    9.     public T t;
    10.     public void AddComponentToEntity(Entity e, EntityManager entityManager)
    11.     {
    12.         entityManager.SetComponentData(e,t);
    13.     }
    14. }
    and your module can be created as follows:
    Code (CSharp):
    1. public class A : UnitModule
    2. {
    3.     public A()
    4.     {
    5.         componentDatas.Add(typeof(LocalToWorld),new ComponentWrapper<LocalToWorld>());
    6.         componentDatas.Add(typeof(MoveSpeed),new ComponentWrapper<MoveSpeed>());
    7.         componentDatas.Add(typeof(Acceleration),new ComponentWrapper<Acceleration>());
    8.     }
    9. }
    That's probably easiest thing you can do. Maybe there's way to make it cleaner but I am a bit tired now and can't think of any improvements. I hope it will help you.

    EDIT: I forgot to give IComponentWrapper source, will do tomorrow.
    EDIT: Added IComponentWrapper source
     
    Last edited: Jun 19, 2020
    Orimay and Tony_Max like this.
  28. BackgroundMover

    BackgroundMover

    Joined:
    May 9, 2015
    Posts:
    216
    I do an auto-add kind of thing where I create an EntityQuery that has a WithAll<ModuleA> and a WithNone<Acceleration>, and pass that query to EntityManager.AddComponent<Acceleration>
     
  29. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    I use your wrapper approach but i have adapdet it to my component preset tool. This way i have my IComponentData[] in scriptable object and extract method that uses Activator to create proper generic wrapper and then use it. All in runtime. This is all looks like dirty hack but i use it only at start, so lets call it "extraction phase" :) So thank you for the idea with wrappers!
     
  30. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    That's true, it's a bit dirty but it saves you a lot of time so it's worth it. Your potential players won't judge your code and you for sure are not going to print your code, frame it at let it hang as a trophy. It works and is short so I would say it's somewhat worth it to play dirty.
     
  31. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    349
    IL2CPP don't want to work with generics instanced by activator. So there is no way to avoid manually code typing with component types for every case i need.
    I think about using codegen. Like new input system works, when u setting up all you need and then u just use huge singleton with all generated stuff