Keeping Entity and GameObject in sync

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.

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 Likes

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.

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:

// Get active EntityManager.
var entityManager = World.Active.GetExistingManager<EntityManager> ();

// Get entity ID from GameObjectEntity component attached to this GameObject.
var entity = GetComponent<GameObjectEntity> ().Entity;

// Add Rigidbody component to this GameObject.
var body = gameObject.AddComponent<Rigidbody> ();

// Register new Rigidbody component with entity.
entityManager.AddComponent (entity, typeof (Rigidbody));

// Unregister Rigidbody component with entity.
entityManager.RemoveComponent (entity, typeof (Rigidbody));

// Remove Rigidbody component on this GameObject.
Destroy (body);

I think Joachim also discussed this a little in his latest talk during GDC.

2 Likes

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.

5 Likes

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.
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.

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?

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.

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

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)

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, shouldn’t it?

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 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.

1 Like

I don’t expect GameObject.AddComponent 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

RigidBody rb = myGameObject.AddComponent<RigidBody>();
entityManager.AddComponent(entity, rb);

or similar, that would be really helpful.

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 :slight_smile:

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 :smile:

1 Like

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.

So here’s a little early Christmas gift for y’all. :smile:

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.

using Unity.Entities;
using UnityEngine;

public static class GameObjectEntityExtensions
{
    /// <summary>
    /// Add Component to GameObject and associated entity (if any).
    /// </summary>
    public static T AddEntityComponent<T>(this GameObject GO) where T : Component
    {
        T component = GO.GetComponent<T>();

        if (component)
        {
            Debug.LogWarning(GO + " already has a " + component.GetType() +
                ". Only one component of each type is supported for entities.");

            // Return already existing component.
            return component;
        }
        else
        {
            // Add new component to GameObject.
            component = GO.AddComponent<T>();
        }

        // Only execute entity related code if required GameObjectEntity component exist.
        var GOEntity = GO.GetComponent<GameObjectEntity>();
        if (!GOEntity)
            return component;

        // Reset GameObjectEntity to rebuild associated entity with newly added component.
        GOEntity.enabled = false;
        GOEntity.enabled = true;

        return component;
    }

    /// <summary>
    /// Remove Component from GameObject and it's associated entity (if any).
    /// </summary>
    public static void RemoveEntityComponent<T>(this GameObject GO) where T : Component
    {
        T component = GO.GetComponent<T>();
        if (component)
        {
            GameObject.Destroy(component);
        }
        else
        {
            Debug.LogWarning("Can not remove " + component + " from " + GO
                + " because it does not exist!");
            return;
        }

        // Only execute entity related code if required GameObjectEntity component exist.
        var GOEntity = GO.GetComponent<GameObjectEntity>();
        if (!GOEntity)
            return;

        // Get the entity associated with this GameObject.
        var entity = GOEntity.Entity;

        // Get entity manager.
        var manager = World.Active.GetOrCreateManager<EntityManager>();

        // Update entity component array.
        manager.RemoveComponent(entity, ComponentType.Create<T>());
    }
}

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:

using Unity.Entities;
using UnityEngine;

[RequireComponent(typeof(GameObjectEntity))]
public class ComponentTest : MonoBehaviour
{
    private Rigidbody m_Body;

    private void Update()
    {
        if (Input.GetKeyUp(KeyCode.Space))
        {
            if (m_Body)
            {
                gameObject.RemoveEntityComponent<Rigidbody>();
            }
            else
            {
                m_Body = gameObject.AddEntityComponent<Rigidbody>();
            }
        }
    }
}

Let me know if you run into any issues with this.

4034485–350626–GameObjectEntityExtensions.cs (2.19 KB)

5 Likes

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. :smile:

@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.

You have to add UpdateInjectedComponentGroups() after “resetting” GameObjectEntity, it will refresh the array.

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.