Search Unity

Keeping Entity and GameObject in sync

Discussion in 'Data Oriented Technology Stack' started by BanJaxe, Mar 30, 2018.

  1. BanJaxe

    BanJaxe

    Joined:
    Nov 20, 2017
    Posts:
    23
    Is there an easy pattern to keep the components on a gameobject and entity the same, so that add/removing a component to one, also changes the other. (Just talking about regular components, not IComponentData)

    Currently you can use GameObjectEntity, but that is just for initialization. e.g. If you then later add a SpriteRenderer to the gameObject the entity doesn't see it. Likewise if you remove a component from the entity the gameObject still has it attached.

    Do you just have to remember to always make the change to both? Maybe I'm not thinking about it correctly as I'm new to ECS.
     
  2. rastlin

    rastlin

    Joined:
    Jun 5, 2017
    Posts:
    100
    Your game code should never do operations directly on GameObject. Your game code should work directly on Entities through components. Last step is that you have some sort of a system which propagates/synchronizes the Entity component updates to the GameObject.

    So the flow is always Entity->GameObject. This approach will provide a number of advantages to your game code:
    * In general it will be fully testable, and your game code can run independently from actual game visuals
    * It will allow you to simulate the changes in the World without being hindered by Unity API's performance or visual updates, because you can easily ignore components which affects a visual aspects of your entities in the simulation
    * You will have single point of synchronization between GameObject and Entity (your sync system) which will naturally batch access to Unity API's (the famous Transform batching trick is a good example)
    * Debugging will be easier, as the origin of component data changes will come all the time from your code

    As the number of components that support Unity API is limited, you will need at some point to add few systems which synchronize data from some Unity components onto your entities, but as UT works on the ECS it should gradually be reduced to zero.
     
  3. BanJaxe

    BanJaxe

    Joined:
    Nov 20, 2017
    Posts:
    23
    Thanks for the reply. I understand what you are saying, but isn't that mostly for pure ECS + job system? I was more thinking about the hybrid pattern.

    I guess what I'm looking for an automatic sync on GameObjectEntity instead of just on OnEnable. If at the end of every frame it automatically adjusted the associated gameobject components to match the entity. The component values themselves don't need to be changed, just the add/remove component commands. That would save you having to write a Sync View system.
     
  4. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    I believe you need to use EntityManager.AddComponent() after using the GameObject.AddComponent() to register the component with the Entity Manger, so it can track it.

    I haven't tested it thoroughly yet, but it seems like the EntityManager's AddComponent method only adds an existing component to the entity, rather than replacing the default AddComponent function completely. Maybe this can be added as a feature in a future version though?

    Here's some test code:

    Code (CSharp):
    1. // Get active EntityManager.
    2. var entityManager = World.Active.GetExistingManager<EntityManager> ();
    3.  
    4. // Get entity ID from GameObjectEntity component attached to this GameObject.
    5. var entity = GetComponent<GameObjectEntity> ().Entity;
    6.  
    7. // Add Rigidbody component to this GameObject.
    8. var body = gameObject.AddComponent<Rigidbody> ();
    9.  
    10. // Register new Rigidbody component with entity.
    11. entityManager.AddComponent (entity, typeof (Rigidbody));
    12.  
    13. // Unregister Rigidbody component with entity.
    14. entityManager.RemoveComponent (entity, typeof (Rigidbody));
    15.  
    16. // Remove Rigidbody component on this GameObject.
    17. Destroy (body);
    I think Joachim also discussed this a little in his latest talk during GDC.
     
    Last edited: Apr 2, 2018
    GliderGuy and optimise like this.
  5. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    4,671
    Right now the behaviour is awkward because it is not live updating.

    The above code example works for now, but of course we want to make this workaround be unnecessary in the future.
     
  6. jooleanlogic

    jooleanlogic

    Joined:
    Mar 1, 2018
    Posts:
    332
    I've tried the above and while it adds a RigidBody component to the Entity, its value is null. It doesn't actually put a reference to the RigidBody in the ComponentArray<RigidBody>.
    Is this meant to work? I'm only just running into this issue now that I'm trying to add physics joint components in real time.
     
  7. IC_

    IC_

    Joined:
    Jan 27, 2016
    Posts:
    31
    I still can't understand am I able to create component data that keeps reference to my game object? I guess I can't because component data is value type structure and I should use MeshInstanceRendererComponent instead? So how can I debug position of that mesh clicking it in the scene view? I could debug it through entity debugger but what if I have a great number of object, like sprite tile world?
     
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    5,417
    For now it is not possible to directly click on rendered entities in the scene, if considering Pure ECS. But you can write own ECS collider detection and raycast it, to get info about tail in Debugger.

    Otherwise via OOP, follow what guys already said, or check Hybrid ECS example twostick.
     
  9. kogen

    kogen

    Joined:
    Dec 2, 2015
    Posts:
    6
    I also cannot confirm that this code fragment works.
    The result is that the system that uses this entity creates a NullReference Exception after debugging the entity it turns out that the newly added component is not present (Null). Are there any updates / error fixings for this workaround thus the systems are able to register the entitites that have newly created components?

    I also tried "EntityManager.Update()" after the above lines but that does not fix anything either.

    Kind regards

    Mirko
     
  10. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,265
    Just adding the component type it is going to be null like just adding an IComponentData and you get a default struct of that component. You need

    EntityManager : internal void SetComponentObject(Entity entity, ComponentType componentType, object componentObject)
    to be able to store a reference to mono component in ECS database. But it is an internal method that GameObjectEntity is using, so it is out of reach from API user. (Unless you use reflection to invoke it)
     
  11. Whippets

    Whippets

    Joined:
    Feb 28, 2013
    Posts:
    1,775
    Is there a resolution to this that doesn't involve hacky use of reflection, or a fix to AddComponent or SetComponent if they're not working as intended? Once you add a component, surely it should become part of the ComponentArray<type>, shouldn't it?
     
  12. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,265
    I think the reason why the component does not sync is because, it has no existing place to sync without adding new functionality to core Unity so we have to wait until then.

    Imagine one random MonoBehaviour code is adding a component to one game object with GameObjectEntity attached. UT could write a code to sync that, but where is the correct place for this code?

    1. Add a new MonoBehaviour callback which get called on component added-removed : requires modifying the core Unity API. Also then this callback has to know ECS package code, the GameObjectEntity class.
    2. Modify AddComponent<T> code to inspect if the object contains GameObjectEntity, if it exist then do something to sync immediately : also requires modifying the core Unity API. And this means the main UnityEngine package will have to link to Entities package. I can imagine they want things to stabilize first before making this kind of hard integration. Currently ECS is an add-on.

    UT might still want to develop more on GameObjectEntity and those wrappers. If they add sync feature early on they might have to undo more work if things have to change. I guess it might be as late as when ECS is completely in the engine with friendly UI, etc.

    As far as I think there is no way to easily enable sync after adding ECS package without more work on the core engine. Contrary to the current OnEnable-only sync which those code can be in the ECS package completely.
     
    Whippets likes this.
  13. Whippets

    Whippets

    Joined:
    Feb 28, 2013
    Posts:
    1,775
    I don't expect GameObject.AddComponent<T> to sync with GameObjectEntity. I'm quite happy to use GameObject.AddComponent and then use GameObjectEntity.AddComponent - as long as GameObjectEntity then grabs the instance of the component, as at the moment it just adds a null reference.

    If I could do
    Code (csharp):
    1.  
    2. RigidBody rb = myGameObject.AddComponent<RigidBody>();
    3. entityManager.AddComponent(entity, rb);
    4.  
    or similar, that would be really helpful.
     
  14. Bhakti_GL1

    Bhakti_GL1

    Joined:
    Jul 4, 2018
    Posts:
    17
    I always "reset" its GameObjectEntity component so that the system can receive the changing one of their entity's component. I hope this is helpful :)


    GameObjectEntity entityGO;

    // Add Component
    entityGO.AddComponent<Something>();

    // Reset
    entityGO.enabled = false;
    entityGO.enabled = true;


    EDITED: Do not try this trick! Sometimes it makes "NativeArray has been deallocated" error occurred. Please find another trick! Thank you :D
     
    Last edited: Dec 19, 2018
    davenirline likes this.
  15. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    489
    That's neat trick. Internally though, this destroys then recreates the entity. This can become an issue if you have systems that keeps track of entities in their separate container. The entity in those container might no longer be the entity that the GameObject represents.
     
  16. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    So here's a little early Christmas gift for y'all. :D

    I finally took another look at this issue and came up with two extension methods that seem to work very well. Just create a new script in your project and add the following code or download the attached script below.

    Update: fixed null component array.

    Code (CSharp):
    1.  
    2. using Unity.Entities;
    3. using UnityEngine;
    4.  
    5. public static class GameObjectEntityExtensions
    6. {
    7.     /// <summary>
    8.     /// Add Component to GameObject and associated entity (if any).
    9.     /// </summary>
    10.     public static T AddEntityComponent<T>(this GameObject GO) where T : Component
    11.     {
    12.         T component = GO.GetComponent<T>();
    13.  
    14.         if (component)
    15.         {
    16.             Debug.LogWarning(GO + " already has a " + component.GetType() +
    17.                 ". Only one component of each type is supported for entities.");
    18.  
    19.             // Return already existing component.
    20.             return component;
    21.         }
    22.         else
    23.         {
    24.             // Add new component to GameObject.
    25.             component = GO.AddComponent<T>();
    26.         }
    27.  
    28.         // Only execute entity related code if required GameObjectEntity component exist.
    29.         var GOEntity = GO.GetComponent<GameObjectEntity>();
    30.         if (!GOEntity)
    31.             return component;
    32.  
    33.         // Reset GameObjectEntity to rebuild associated entity with newly added component.
    34.         GOEntity.enabled = false;
    35.         GOEntity.enabled = true;
    36.  
    37.         return component;
    38.     }
    39.  
    40.     /// <summary>
    41.     /// Remove Component from GameObject and it's associated entity (if any).
    42.     /// </summary>
    43.     public static void RemoveEntityComponent<T>(this GameObject GO) where T : Component
    44.     {
    45.         T component = GO.GetComponent<T>();
    46.         if (component)
    47.         {
    48.             GameObject.Destroy(component);
    49.         }
    50.         else
    51.         {
    52.             Debug.LogWarning("Can not remove " + component + " from " + GO
    53.                 + " because it does not exist!");
    54.             return;
    55.         }
    56.  
    57.         // Only execute entity related code if required GameObjectEntity component exist.
    58.         var GOEntity = GO.GetComponent<GameObjectEntity>();
    59.         if (!GOEntity)
    60.             return;
    61.  
    62.         // Get the entity associated with this GameObject.
    63.         var entity = GOEntity.Entity;
    64.  
    65.         // Get entity manager.
    66.         var manager = World.Active.GetOrCreateManager<EntityManager>();
    67.  
    68.         // Update entity component array.
    69.         manager.RemoveComponent(entity, ComponentType.Create<T>());
    70.     }
    71. }
    72.  
    73.  
    Now all you need to do is use AddEntityComponent and RemoveEntityComponent instead of AddComponent and Destroy respectively in your MonoBehaviour scripts. Here is an example script I made to test them:

    Code (CSharp):
    1.  
    2. using Unity.Entities;
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(GameObjectEntity))]
    6. public class ComponentTest : MonoBehaviour
    7. {
    8.     private Rigidbody m_Body;
    9.  
    10.     private void Update()
    11.     {
    12.         if (Input.GetKeyUp(KeyCode.Space))
    13.         {
    14.             if (m_Body)
    15.             {
    16.                 gameObject.RemoveEntityComponent<Rigidbody>();
    17.             }
    18.             else
    19.             {
    20.                 m_Body = gameObject.AddEntityComponent<Rigidbody>();
    21.             }
    22.         }
    23.     }
    24. }
    25.  
    26.  
    Let me know if you run into any issues with this.
     

    Attached Files:

    Last edited: Dec 27, 2018
  17. jooleanlogic

    jooleanlogic

    Joined:
    Mar 1, 2018
    Posts:
    332
    When I try this, although it adds the RigidBody component, it is still null within the ComponentArray.
    Your example ComponentTestSystem just checks the array Length property, not the value of bodies.

    Hopefully I've overlooked something as I'd love for this to work and for you not to be the grinch of christmas. :D
     
  18. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    @jooleanlogic bah humbug, you're actually correct. I posted the code prematurely out of excitement without testing it properly first. It has now been "fixed". Not sure if this is the optimal way to do it yet, but at least it now functions as expected. You may however still run into issues like the one @Bhakti_GL1 had. In which case some kind of component updating system might be the only way to go.

    A few interesting issues I noticed in my tests here: First is, as you've pointed out, the fact that EntityManager.AddComponent does update the entity component array but the reference is always null. After examining the source I discovered that GameObjectEntity calls a method named AddToEntityManager on the OnEnable callback, which creates an entity based on a GameObject's components. This method is public so you can call it yourself when adding a new component but it's easiest to just "reset" GameObjectEntity by disabling and re-enabling it to force it to call that method itself. So this is what I now do in my extension code above.

    However, I also noticed that "resetting" GameObjectEntity doesn't work when it comes to removing components!? It will remove the component but not update the component array. So you will end up with a null component array again like with EntityManager.AddComponent. Fortunately, in this cause using EntityManager.RemoveComponent actually works for some strange reason. So you can just use that.

    Overall, I'm afraid it looks like you have to destroy and recreate the entity every time you want to add a new component to the GameObject. I think this is because GameObjectEntity actually creates an entity archetype based on the GameObject. Which is more optimized but less flexible (can't add/remove components to entity easily), at least that's what I believe. still learning all of this.
     
  19. Bhakti_GL1

    Bhakti_GL1

    Joined:
    Jul 4, 2018
    Posts:
    17
    You have to add UpdateInjectedComponentGroups() after "resetting" GameObjectEntity, it will refresh the array.
     
  20. primitiveconcept

    primitiveconcept

    Joined:
    Feb 23, 2013
    Posts:
    8
    While it's still a hacky reflection approach, I wonder if reflecting the previously mentioned internal SetComponentObject, then caching that in an Expression (that could then be accessed statically from an extension method) might be a good approach until a better native solution comes along.
     
  21. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    tertle and primitiveconcept like this.
  22. primitiveconcept

    primitiveconcept

    Joined:
    Feb 23, 2013
    Posts:
    8
    Hah! I was just wrapping up my own implementation, but their approach for creating the delegate was much simpler (I was manually putting together a compiled Expression first, which was probably overkill).

    Kudos, @tertle ! And thanks for the heads up @IsaiahKelly
     
    tertle likes this.
  23. primitiveconcept

    primitiveconcept

    Joined:
    Feb 23, 2013
    Posts:
    8
    Here's the result, combining approaches from both @tertle and @IsaiahKelly
    After a quick initial test, it seems to work.

    Code (CSharp):
    1.  
    2. using System;
    3. using Unity.Entities;
    4. using UnityEngine;
    5.  
    6.  
    7. public static class GameObjectEntityExtensions
    8. {
    9.    private static Action<Entity, ComponentType, object> setComponentObjectDelegate;
    10.  
    11.  
    12.    /// <summary>
    13.    /// Add a Component to a GameObjectEntity.
    14.    /// </summary>
    15.    /// <param name="gameObjectEntity"><see cref="GameObjectEntity"/>.</param>
    16.    /// <typeparam name="T">Type of Component to add.</typeparam>
    17.    /// <returns>Added Component.</returns>
    18.    public static T AddComponent<T>(this GameObjectEntity gameObjectEntity)
    19.       where T: Component
    20.    {
    21.       EntityManager entityManager = World.Active.GetExistingManager<EntityManager>();
    22.       Entity entity = gameObjectEntity.Entity;
    23.       T component = gameObjectEntity.gameObject.AddComponent<T>();      
    24.       entityManager.AddComponent(entity, typeof(T));
    25.       entityManager.SetComponentObject(entity, ComponentType.Create<T>(), component);
    26.      
    27.       return component;
    28.    }
    29.  
    30.  
    31.    /// <summary>
    32.    /// Remove a Component from a GameObjectEntity.
    33.    /// </summary>
    34.    /// <param name="gameObjectEntity"><see cref="GameObjectEntity"/>.</param>
    35.    /// <param name="component">Component to remove.</param>
    36.    /// <typeparam name="T">Type of Component to remove.</typeparam>
    37.    public static void RemoveComponent<T>(this GameObjectEntity gameObjectEntity, T component)
    38.       where T: Component
    39.    {
    40.       EntityManager entityManager = World.Active.GetExistingManager<EntityManager> ();
    41.       Entity entity = gameObjectEntity.Entity;
    42.       entityManager.RemoveComponent(entity, typeof(T));
    43.       GameObject.Destroy(component);
    44.    }
    45.  
    46.  
    47.    /// <summary>
    48.    /// Cached reflection for the internal method SetComponentObject within the EntityManager.
    49.    /// </summary>
    50.    /// <param name="entityManager"><see cref="EntityManager"/>.</param>
    51.    /// <param name="entity">The <see cref="Entity"/> to set the object to.</param>
    52.    /// <param name="componentType">The <see cref="ComponentType"/> of the object to set.</param>
    53.    /// <param name="componentObject">The object to set.</param>
    54.    public static void SetComponentObject(
    55.       this EntityManager entityManager,
    56.       Entity entity,
    57.       ComponentType componentType,
    58.       object componentObject)
    59.    {
    60.       if (setComponentObjectDelegate == null)
    61.       {
    62.          setComponentObjectDelegate = Delegate.CreateDelegate(
    63.                type: typeof(Action<Entity, ComponentType, object>),
    64.                target: entityManager,
    65.                method: "SetComponentObject",
    66.                ignoreCase: false)
    67.             as Action<Entity, ComponentType, object>;
    68.       }
    69.  
    70.       if (setComponentObjectDelegate != null)
    71.          setComponentObjectDelegate(entity, componentType, componentObject);
    72.       else
    73.          throw new NullReferenceException("SetComponentObject method signature changed");
    74.    }
    75. }
    76.  
     
  24. jooleanlogic

    jooleanlogic

    Joined:
    Mar 1, 2018
    Posts:
    332
    Yeay this does seem to work. :)
    Thank you to all who inputted.
     
  25. lorddanger

    lorddanger

    Joined:
    Aug 8, 2015
    Posts:
    72
    Hello, I am new to ecs.
    I was wondering that in Hybrid ECS is it possible that the entities using monobehaviour script for some functions?