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

Best practice for associating Entity with a GameObject?

Discussion in 'Entity Component System' started by jwvanderbeck, Jan 6, 2020.

  1. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    While I have all these systems, at the end of it all I still need to generate some GameObjects from entities so that they can be represented using traditional Unity stuff that doesn't exist in DOTS yet.

    Right now in my testing I've just blindly created these GOs without worrying about maintaining any link back, but that's wasteful, as if I want to update the GO state I have to destroy everything and rebuild.

    What would be better would be to maintain some form of connection, even if its just one way Entity->GO so I know what belongs to what.

    At first I thought I could do something like just having a component that stores a string which is the name of a GO, but apparently I can't use strings as components.

    What is the best way to currently do this?
     
  2. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    For Entity->GO, you could have a ComponentSystem that uses Entities.ForEach to iterate through a MonoBehaviour called "GameObjectReference" and then call gameObjectReference.gameObject.

    For GO->Entity, you can make use of these two scripts:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using Unity.Entities;
    4.  
    5. [RequiresEntityConversion]
    6. public class EntitySharer : MonoBehaviour, IConvertGameObjectToEntity
    7. {
    8.     [SerializeField]
    9.     private EntityStore store;
    10.  
    11.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    12.     {
    13.         store.entity = entity;
    14.     }
    15. }
    16.  
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Entities;
    3.  
    4. public class EntityStore : MonoBehaviour
    5. {
    6.     public Entity entity
    7.     {
    8.         get;
    9.         set;
    10.     }
    11. }
    12.  
    Attach EntitySharer to your GameObject-Entity, and EntityStore to your GameObject. Drag the GameObject to your GameObject-Entity, and after conversion the entity will now be stored in the GameObject's EntityStore script. Have your other scripts require a RequireComponent(typeof(EntityStore)) and they can use the entity as well
     
  3. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    I'm sorry but I don't understand what you mean. How is a Monobehaviour existing inside a Component System?
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    I do stuff like this now when I need to interact with gameobjects

    Code (CSharp):
    1. public class GameObjectContainer : IComponentData // notice it is a class
    2. {
    3.     public GameObject GameObject;
    4. }
    5.  
    6. public class SyncGameObject : JobComponentSystem
    7. {
    8.     public override JobHandle OnUpdate(JobHandle handle)
    9.     {
    10.         Entities
    11.         .WithoutBurst()
    12.         .ForEach((GameObjectContainer gameObjectContainer, in Translation translation) =>
    13.         {
    14.             gameObjectContainer.GameObject.transform.position = translation.value;
    15.         })
    16.         .Run();
    17.  
    18.         return handle;
    19.     }
    20. }
    (Quickly written in notepad so probably has a syntax error, especially the lambda)
     
    banjoFrette and T-Zee like this.
  5. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    To enable serialization and deserialization of worlds while keeping track of my Entity<->GameObject relations, I have two classes
    For static GameObjects that are created in-editor and never destroyed:
    Code (CSharp):
    1. using EverydayEngine;
    2. using KinematicCharacterController;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Text;
    6. using Unity.Entities;
    7. using UnityEngine;
    8. public static class GUIDGenerator {
    9.     private static readonly Dictionary<string, GameObject> GUIDToObj = new Dictionary<string, GameObject>();
    10.     private static readonly Dictionary<GameObject, string> ObjToGUID = new Dictionary<GameObject, string>();
    11.     public static void GemerateGUID(GameObjectToEntity target) {
    12.         EnsureInit();
    13.         if (target.GUID != null && target.GUID != "") {
    14.             if (GUIDToObj.ContainsKey(target.GUID)) {
    15.                 if (GUIDToObj[target.GUID] != target.gameObject || ObjToGUID[target.gameObject] != target.GUID) {
    16.                     Debug.LogError("GameObjectToEntity has GUID but is not mapped. Mapping regenerated", target);
    17.                 } else {
    18.                     return;
    19.                 }
    20.             }
    21.         }
    22.         string guid = Guid.NewGuid().ToString();
    23.         while (GUIDToObj.ContainsKey(guid)) {
    24.             guid = Guid.NewGuid().ToString();
    25.         }
    26.         GUIDToObj[ObjToGUID[target.gameObject] = guid] = target.gameObject;
    27.         target.GUID = guid;
    28.     }
    29.  
    30.     private static void EnsureInit() {
    31.         if (GUIDToObj.Count == 0) {
    32.             UnityEngine.Object[] objs = UnityEngine.Object.FindObjectsOfType(typeof(GameObjectToEntity));
    33.             foreach (UnityEngine.Object obj in objs) {
    34.                 GameObjectToEntity ote = ((GameObjectToEntity)obj);
    35.                 if (ote.GUID != null && ote.GUID != "") {
    36.                     ObjToGUID[GUIDToObj[ote.GUID] = ote.gameObject] = ote.GUID;
    37.                 }
    38.             }
    39.             foreach (UnityEngine.Object obj in objs) {
    40.                 GameObjectToEntity ote = ((GameObjectToEntity)obj);
    41.                 if (ote.GUID == null && ote.GUID == "") {
    42.                     GUIDGenerator.GemerateGUID(ote);
    43.                 }
    44.             }
    45.         }
    46.     }
    47.  
    48.     internal static void Delete(GameObject gameObject) {
    49.         EnsureInit();
    50.         if (ObjToGUID.ContainsKey(gameObject)) {
    51.             GUIDToObj.Remove(ObjToGUID[gameObject]);
    52.             ObjToGUID.Remove(gameObject);
    53.         }
    54.     }
    55.  
    56.     internal static GameObject GetGameObjectFromGUID(string guid) {
    57.         EnsureInit();
    58.         return GUIDToObj[guid];
    59.     }
    60. }
    61. [ExecuteInEditMode]
    62. public class GameObjectToEntity : MonoBehaviour {
    63.     [ReadOnlyAttribute]
    64.     public string GUID;
    65.     public Entity Entity;
    66. #if UNITY_EDITOR
    67.     public static bool DISABLE_CHECK;
    68.     [HideInInspector]
    69.     public bool justCreated;
    70. #endif
    71.  
    72.     public GameObjectToEntity() {
    73. #if UNITY_EDITOR
    74.         justCreated = true;
    75. #endif
    76.     }
    77.     private void Awake() {
    78.         if (!UnityEngine.Application.isPlaying) {
    79.             GUIDGenerator.GemerateGUID(this);
    80.         }
    81.     }
    82.     private void Start() {
    83. #if UNITY_EDITOR
    84.         if (UnityEngine.Application.isPlaying) {
    85.             if (justCreated) {
    86.                 Debug.LogError("GameObjectToEntity created on runtime. Use RuntimeGameObjectToEntity during runtime instead", gameObject);
    87.             }
    88. #endif
    89.             EntityManager em = World.Active.EntityManager;
    90.             Entity = em.CreateEntity();
    91. #if UNITY_EDITOR
    92.             DISABLE_CHECK = true;
    93. #endif
    94.             em.AddComponentObject(Entity, new EntityToGameObject(gameObject));
    95. #if UNITY_EDITOR
    96.             DISABLE_CHECK = false;
    97.         }
    98.         justCreated = false;
    99. #endif
    100.     }
    101.     private void OnDestroy() {
    102.         GUIDGenerator.Delete(gameObject);
    103. #if UNITY_EDITOR
    104.         if (UnityEngine.Application.isPlaying) {
    105. #endif
    106.             World.Active?.EntityManager.RemoveComponent<EntityToGameObject>(Entity);
    107. #if UNITY_EDITOR
    108.         }
    109. #endif
    110.     }
    111. }
    112.  
    113. public class EntityToGameObject : Component, AutoSaveComponent {
    114.     public byte[] GenerateData(Entity e) { return Encoding.UTF8.GetBytes(GameObject.GetComponent<GameObjectToEntity>().GUID); }
    115.     public void LoadFromData(byte[] data, EntityRecoverer recover, Entity e) {
    116.         GameObject = GUIDGenerator.GetGameObjectFromGUID(Encoding.UTF8.GetString(data));
    117.         GameObject.GetComponent<GameObjectToEntity>().Entity = e;
    118.         foreach (IReloadCacheOnLoad reloader in GameObject.GetComponents<IReloadCacheOnLoad>()) {
    119.             Save.PostLoadCalls(() => reloader.ReloadCache());
    120.         }
    121.     }
    122.     public GameObject GameObject;
    123.  
    124.     public EntityToGameObject() {
    125. #if UNITY_EDITOR
    126.         if (!GameObjectToEntity.DISABLE_CHECK && UnityEngine.Application.isPlaying) {
    127.             Debug.LogError("GameObjectToEntity created on runtime. Use RuntimeEntityToGameObject during runtime instead");
    128.         }
    129. #endif
    130.     }
    131.     public EntityToGameObject(GameObject GameObject) : this() {
    132.         this.GameObject = GameObject;
    133.     }
    134. }
    135.  
    136. public interface IReloadCacheOnLoad {
    137.     void ReloadCache();
    138. }
    And for GameObjects created during runtime. In this case, the IComponentData needs to retain all the relevant information to recreate this GameObject from zero after loading it's data, I've made a couple of other types and classes to help with that, but it's irrelevant for this question
    Code (CSharp):
    1. using KinematicCharacterController;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using Unity.Entities;
    6. using UnityEngine;
    7. public class RuntimeGameObjectToEntity : GameObjectToEntity {
    8.     private byte init;
    9.  
    10.     public RuntimeGameObjectToEntity() {
    11.     }
    12.     private void Start() {
    13.         Init();
    14.     }
    15.  
    16.     public void Init(Entity entity=default) {
    17.         if (init == 0) {
    18.             init = 1;
    19.             EntityManager em = World.Active.EntityManager;
    20.             if (entity == default) {
    21.                 Entity = em.CreateEntity();
    22.             } else {
    23.                 Entity = entity;
    24.             }
    25.             em.AddComponentObject(Entity, new RuntimeEntityToGameObject(gameObject));
    26.         }
    27.     }
    28.  
    29.     private void OnDestroy() {
    30.         World.Active.EntityManager.RemoveComponent<RuntimeEntityToGameObject>(Entity);
    31.     }
    32. }
    33.  
    34. public class RuntimeEntityToGameObject : Component {
    35.     public GameObject GameObject;
    36.     public RuntimeEntityToGameObject() {
    37.     }
    38.     public RuntimeEntityToGameObject(GameObject GameObject) {
    39.         this.GameObject = GameObject;
    40.     }
    41. }
     
  6. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    A ComponentSystem can iterate through entities that have a Monobehaviour attached to it. From that Monobehaviour you can access the GameObject that was injected into the entity
     
  7. phreezie

    phreezie

    Joined:
    Oct 3, 2019
    Posts:
    119
    Are you still doing that in 1.0? :)

    Migrating from 0.17, and I used to keep a lookup table between entities and game objects (not using Entities.Graphics) that was populated during conversion, which is now done at editor time, so that's kind of out of the question here.
     
    Last edited: Feb 1, 2023
  8. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Yes that is still the general way to do it.
    I explained it in my tutorial/course while talking about animation and hybrid approach.
     
    phreezie likes this.