Search Unity

How to Add/RemoveComponent() in Hybrid ECS?

Discussion in 'Entity Component System' started by afonseca, Apr 25, 2018.

  1. afonseca

    afonseca

    Joined:
    Feb 28, 2012
    Posts:
    80
    Using Hybrid ECS I'm looking to add/remove components from an entity in the OnUpdate() loop within a ComponentSystem class. However, I'm not sure how to get the entity object to pass the the AddComponent() / RemoveComponent() methods.

    Here's an example modeled after the sample in the docs:

    Code (CSharp):
    1. class SomeComponent : MonoBehaviour
    2. {
    3.    public int Value;
    4. }
    5.  
    6. class SomeSystem : ComponentSystem
    7. {
    8.    struct Group
    9.    {
    10.        public SomeComponent SomeComponent
    11.    }
    12.  
    13.    protected override OnUpdate()
    14.    {
    15.        foreach (var e in GetEntities<Group>())
    16.        {
    17.            // not sure how to do this as e is not the right type...
    18.            //PostUpdateCommands.RemoveComponent<SomeComponent>(entity);
    19.  
    20.        }
    21.    }
    22. }
    I'm probably missing something basic here but couldn't find anything searching around and still trying to figure out how this all works.
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Code (CSharp):
    1.  
    2. public class SomeSystem : ComponentSystem
    3. {
    4.     struct SomeGroup
    5.     {
    6.         public ComponentArray<Building> Component; //Building is MonoBehaviour component
    7.         public EntityArray Entity;
    8.         public int Length;
    9.     }
    10.  
    11.     [Inject] SomeGroup group;
    12.  
    13.     protected override void OnUpdate()
    14.     {
    15.         //for add ECS components in one loop
    16.         for (int i = 0; i < group.Length; i++)
    17.         {
    18.             PostUpdateCommands.AddComponent(group.Entity[i], new SomeComponentData());
    19.         }
    20.  
    21.         //for remove ECS\MonoBehaviout components in one loop
    22.         for (int i = 0; i < group.Length; i++)
    23.         {
    24.             PostUpdateCommands.RemoveComponent<Building>(group.Entity[i]);
    25.         }
    26.  
    27.         //But remember - this is NOT adding\removing components to GameObject, only to Entity
    28.     }
    29. }
    30.  
    31. public struct SomeComponentData : IComponentData
    32. {
    33.     //some blittable stuff
    34. }
    35.  
     
    alamac123, Singtaa and afonseca like this.
  3. afonseca

    afonseca

    Joined:
    Feb 28, 2012
    Posts:
    80
    @eizenhorn thank you for the sample, that is very helpful!

    I am curious, does anyone know if it's required to use the [Inject] syntax to add/remove components as opposed to the GetEntities() loop? I think that's where I got stuck as I opted to go with the loop method but don't know if there's syntax to do the same thing as shown above?

    If not, then maybe I'll just switch to using the [Inject] method everywhere for consistency.
     
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    GetEntities gives you ComponentGroupArray, ComponentGroup can't has Entity (as i know).
    GetEntities lets us efficiently iterate over all GameObjects (<- Important!) is simple way to update MonoBehaviour components data, but not for add\remove.
     
    Last edited: Apr 25, 2018
  5. any_user

    any_user

    Joined:
    Oct 19, 2008
    Posts:
    374
    I'm trying to use Hybrid ECS and I'm also running into some issues with adding/removing components.

    Is there a way to add/remove components and keeping the it synchonized between GameObject and entity? I tried it in a few ways, but it seems that gameObject.AddComponent<T>() doesn't get picked up by the entity and adding a component to an entity doesn't add it to the game object.

    For example i tried to add a component to the GameObject and the entity separately. Then I wanted to update it on the entity to the object of the component attached to the GameObject, so they're the same object. Unfortunately it doesn't work, because the SetComponentObject function in EntityManager is internal. After trying a few variations of this, I noticed that adding a MonoBehaviour component to an entity doesn't actually add anything, so I'm not sure anymore if my approach makes sense at all.

    Code (CSharp):
    1.  
    2. var goe = gameObject.GetComponent<GameObjectEntity>();
    3. goe.EntityManager.AddComponent(goe.Entity,ComponentType.Create<SomeMB>());
    4.  
    5. var comp = goe.EntityManager.GetComponentObject<SomeMB>(goe.Entity);
    6.  
    7. Debug.Log(comp); // comp is null
    Why is the component null right after adding it? Is this supposed to work?

    My current workaround to add components in Hybrid ECS and GameObject world synchronously looks like this. By disabling and enabling the object, the entity is destroyed and created again. Obviously not very performant, but it does the job.
    Code (CSharp):
    1.  
    2.  go.AddComponent<T>();
    3.  
    4. go.gameObject.SetActive(false);
    5. go.gameObject.SetActive(true);
     
  6. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,129
    From what I know so far, currently it does not support sync the components of Game Object and the components of Entity together seamlessly yet.
     
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    not exactly, I'm not at the computer now, I'll write it out a little later.
     
  8. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    SetActive(true\false) it's a first what i do for this, but, as i think, is crutch. Some part of information you can find in this post. Now I create new objects (in a hybrid approach) and add components (with synchronization) to them in this way:
    Code (CSharp):
    1.  var citizen = UnityEngine.Object.Instantiate(Hub.hub.womanPrefab, pos, Quaternion.identity);
    2.                     var citizenData = citizen.AddComponent<Citizen>();
    3.                     citizenData.data = new CitizenData
    4.                     {
    5.                         citizenId = Hub.lastCitizenId,
    6.                         resources = new ResourcesOnCitizen
    7.                         {
    8.                             hasResource = false,
    9.                             food = 0,
    10.                             wood = 0,
    11.                             stone = 0,
    12.                             iron = 0
    13.                         }
    14.                     };
    15.                     GameObjectEntity.AddToEntityManager(EntityManager, citizen);
    I remind you that these are all variations for the hybrid approach. For pure ECS, I did not observe any synchronization problems, everything is trivially added, changed, etc.

    Answer for this you can find here in my thread.
     
    shiny_shoes1 and florianhanke like this.
  9. afonseca

    afonseca

    Joined:
    Feb 28, 2012
    Posts:
    80
    Quick follow-up as I'm having trouble with this bit of syntax:

    PostUpdateCommands.AddComponent() requires a type so in this example the call would be something like this I think:
    Code (CSharp):
    1.             PostUpdateCommands.AddComponent<SomeComponentData>(group.Entity[i], new SomeComponentData());
    However, when I try this in my own very similar code, I get this type of compile error:

    error CS0453: The type `SomeComponentData' must be a non-nullable value type in order to use it as type parameter `T' in the generic type or method `Unity.Entities.EntityCommandBuffer.AddComponent<T>(Unity.Entities.Entity, T)'


    I've tried this with my version of SomeComponentData deriving from either MonoBehavior or IComponentData, doesn't seem to make the error go away. Am I missing something here?
     
  10. afonseca

    afonseca

    Joined:
    Feb 28, 2012
    Posts:
    80
    I found my mistake, my component was a class deriving from MonoBehavior so to get rid of the error it needs to be a struct deriving from IComponentData.

    It sounds like the ability to add a MonoBehavior component to an entity during runtime for Hybrid ECS is on the 'to-do' list for Unity. Please someone correct me if that's not the case.
     
  11. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Watch my last answer, where Citizen is mono component
     
  12. afonseca

    afonseca

    Joined:
    Feb 28, 2012
    Posts:
    80
    Got it, thank you. I'll have to try that when I get a chance.
     
  13. coldasfrost979

    coldasfrost979

    Joined:
    Jun 9, 2018
    Posts:
    25
    I am trying to do something similar. So far I have
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using static ComponentTypes;
    6. using Unity.Collections;
    7.  
    8. public class MouseOverSystem : ComponentSystem
    9. {
    10.     Ray ray;
    11.     RaycastHit hit;
    12.  
    13.     public struct MouseOverTargetGroup
    14.     {
    15.         public ComponentArray<BoxCollider> collider;
    16.         [ReadOnly] public ComponentDataArray<MouseOverTarget> mouseOverTarget;
    17.         public EntityArray Entity;
    18.         public int Length;
    19.     }
    20.  
    21.     [Inject] MouseOverTargetGroup group;
    22.  
    23.     protected override void OnUpdate()
    24.     {
    25.         Debug.Log("did  I add it?");
    26.         ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    27.        
    28.         for (int i = 0; i < group.Length; i++)
    29.         {
    30.             if (group.collider[i].Raycast(ray, out hit, 1000.0f))
    31.             {
    32.                 Debug.Log("did  I add it?");
    33.                FallenBootstrap.entityManager.AddComponent(group.Entity[i], ComponentType.Create<MouseOver>());
    34.             }
    35.         }
    36.     }
    37. }
    38.  
    My bootstrap class, FallenBootstrap is:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using UnityEngine.AI;
    6. using Unity.Transforms;
    7. using static BuildingProductionSystem;
    8. using static ComponentTypes;
    9.  
    10. public class FallenBootstrap  {
    11.     public static EntityManager entityManager;
    12.     private static EntityArchetype cameraArchetype;
    13.     private static EntityArchetype buildingArchetype;
    14.     private static EntityArchetype unitArchetype;
    15.     private static EntityArchetype groundArchetype;
    16.  
    17.    
    18.     // Use this for initialization
    19.     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    20.     public static void Start () {
    21.         Debug.Log("I have started the game!");
    22.         entityManager = World.Active.GetOrCreateManager<EntityManager>();
    23.  
    24.         cameraArchetype = entityManager.CreateArchetype(typeof(Rotation), typeof(Position), typeof(Camera));
    25.  
    26.  
    27.         buildingArchetype = entityManager.CreateArchetype(typeof(MeshRenderer), typeof(BoxCollider), typeof(BuildingInputSystem),
    28.             typeof(RectTransform), typeof(MeshFilter), typeof(MouseOverSystem), typeof(Position), typeof(MouseOverTarget));
    29.  
    30.         unitArchetype = entityManager.CreateArchetype(typeof(MeshRenderer), typeof(SphereCollider), typeof(NavMeshAgent));
    31.  
    32.         groundArchetype = entityManager.CreateArchetype(typeof(MeshRenderer), typeof(MeshCollider));
    33.  
    34.         Entity building = entityManager.CreateEntity(buildingArchetype);
    35.  
    36.         var proto = GameObject.Find("Building");
    37.         //var proto_position = proto.GetComponent<Position>().Value; //why are these different?
    38.         //var proto_rect_transform = proto.GetComponent<RectTransform>();
    39.         //Object.Destroy(proto);
    40.  
    41.       //  entityManager.SetComponentData(building, proto_position);
    42.        
    43.  
    44.      //   var Camera = entityManager.CreateEntity(cameraArchetype);
    45.    
    46.     }
    47.    
    48. }
    49.  
    and finally I have my structs...
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using Unity.Entities;
    4.  
    5. public class ComponentTypes
    6. {
    7.     public struct MouseOverTarget : IComponentData
    8.     {
    9.        
    10.     }
    11.  
    12.     public struct MouseOver : IComponentData
    13.     {
    14.  
    15.     }
    16.  
    17.     public struct ClickTarget : IComponentData
    18.     {
    19.  
    20.     }
    21.  
    22.     public struct Clicked : IComponentData
    23.     {
    24.  
    25.     }
    26.  
    27.     public struct ClickHandled : IComponentData
    28.     {
    29.  
    30.     }
    31. }
    32.  

    unfortunately whenever I run my code I get the following:
    NullReferenceException: Object reference not set to an instance of an object
    MouseOverSystem.OnUpdate () (at Assets/GameCode/Shared/MouseOverSystem.cs:30)
    Unity.Entities.ComponentSystem.InternalUpdate () (at [redacted]/packages.unity.com/com.unity.entities@0.0.12-preview.5/Unity.Entities/ComponentSystem.cs:290)
    Unity.Entities.ScriptBehaviourManager.Update () (at [redacted]/packages/packages.unity.com/com.unity.entities@0.0.12-preview.5/Unity.Entities/ScriptBehaviourManager.cs:82)
    Unity.Entities.ScriptBehaviourUpdateOrder+DummyDelagateWrapper.TriggerUpdate () (at [redacted]/packages/packages.unity.com/com.unity.entities@0.0.12-preview.5/Unity.Entities/ScriptBehaviourUpdateOrder.cs:732)



    I am not sure what I am doing wrong here. Any advice?
     
  14. omegabytestudio

    omegabytestudio

    Joined:
    Nov 9, 2015
    Posts:
    77
    For anyone having this issue or if you still have it :

    Your IComponentData need at least a value inside.

    Don't ask me why, it's my work around.

    Code (CSharp):
    1. public struct AIBusy : IComponentData {
    2. }
    Didn't work.

    Code (CSharp):
    1. public struct AIBusy : IComponentData {
    2.     public int test;
    3. }
    Worked!
     
  15. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    IComponentData don't have to, have value.
    Without value can be treated as a tag.
     
  16. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Looks like the Group.collider instance value may be null for some reason. Verify they exist first, then perhaps report a bug?

    Which version of the ECS package are you on? Your group struct may need to change
    public int Length;
    to
    public readonly int Length;
    if it's one of the newer ones.

    The components being tags or not seems irrelevant on first glance.