Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How to instantiate an entity prefab from UI button click?

Discussion in 'Entity Component System' started by MaskedMouse, Dec 11, 2022.

  1. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,058
    I haven't touched ECS in a while and I saw entities 1.0 being in preview now.
    So I wanted to do something simple. I'm trying to get myself familiar with the new API.

    I created a
    Baker
    that converts a
    GameObject
    prefab into an
    Entity
    and use
    AddComponent
    to set
    PrefabSpawnRequest.Prefab
    entity and the
    PrefabSpawnRequest.Amount
    .
    An
    ISystem
    will process the
    PrefabSpawnRequest
    and then destroy the entity.
    But this is a single time and only does this at start.

    Now I have a UI made with UI Toolkit that has an integer field and a button.
    I want to instantiate the requested amount after clicking the button.
    But how do I give the entity prefab as reference to the UI?
    Or perhaps I should approach it differently but I wouldn't know how.
     
  2. Jonas_DM_

    Jonas_DM_

    Joined:
    Feb 28, 2019
    Posts:
    22
    There are lots of approaches, here's one where the system is stateless & is bursted.
    (This is example code, I wrote it so that it compiles, but I haven't tested it)

    Code (CSharp):
    1.  
    2. namespace Example
    3. {
    4.     // ----------------------------------------------------------------------------------------
    5.     // In the Button grab the PrefabSpawnRequest Singleton, grab the buffer & add your requests
    6.     // ----------------------------------------------------------------------------------------
    7.  
    8.     public enum SpawnCode
    9.     {
    10.         Cube,
    11.         Bullet,
    12.         Enemy
    13.     }
    14.  
    15.     public struct PrefabSpawnRequest : IBufferElementData
    16.     {
    17.         public SpawnCode SpawnCode;
    18.         public Translation Translation;
    19.         public Rotation Rotation;
    20.     }
    21.  
    22.     public struct PrefabSpawnSpecification : IComponentData
    23.     {
    24.         public SpawnCode SpawnCode;
    25.         public Entity PrefabEntity;
    26.     }
    27.  
    28.     [BurstCompile]
    29.     public partial struct SpawnerSystem : ISystem
    30.     {
    31.         private Entity _requestBufferEntity;
    32.      
    33.         public void OnCreate(ref SystemState state)
    34.         {
    35.             _requestBufferEntity = state.EntityManager.CreateEntity(typeof(PrefabSpawnRequest));
    36.         }
    37.  
    38.         [BurstCompile]
    39.         public void OnDestroy(ref SystemState state)
    40.         {
    41.             state.EntityManager.DestroyEntity(_requestBufferEntity);
    42.         }
    43.  
    44.         [BurstCompile]
    45.         public void OnUpdate(ref SystemState state)
    46.         {
    47.             var dynamicBuffer = state.EntityManager.GetBuffer<PrefabSpawnRequest>(_requestBufferEntity);
    48.             if (dynamicBuffer.IsEmpty)
    49.                 return;
    50.          
    51.             var requests = new NativeArray<PrefabSpawnRequest>(dynamicBuffer.Length, Allocator.TempJob);
    52.             requests.CopyFrom(dynamicBuffer.AsNativeArray());
    53.             dynamicBuffer.Clear();
    54.          
    55.             new SpawnJob
    56.             {
    57.                 Requests = requests,
    58.                 Ecb = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged)
    59.             }.Schedule();
    60.         }
    61.  
    62.         public partial struct SpawnJob : IJobEntity
    63.         {
    64.             [DeallocateOnJobCompletion]
    65.             public NativeArray<PrefabSpawnRequest> Requests;
    66.          
    67.             public EntityCommandBuffer Ecb;
    68.          
    69.             public void Execute(in PrefabSpawnSpecification specification)
    70.             {
    71.                 for (int i = 0; i < Requests.Length; i++)
    72.                 {
    73.                     var request = Requests[i];
    74.                     if (request.SpawnCode != specification.SpawnCode)
    75.                         continue;
    76.                  
    77.                     var entity = Ecb.Instantiate(specification.PrefabEntity);
    78.                     Ecb.SetComponent(entity, request.Translation);
    79.                     Ecb.SetComponent(entity, request.Rotation);
    80.                 }
    81.             }
    82.         }
    83.     }
    84. }
    85.  
     
    Last edited: Dec 12, 2022
    MaskedMouse and LzzzQ like this.
  3. BenjaminApprill

    BenjaminApprill

    Joined:
    Aug 1, 2022
    Posts:
    106
    If your UI code is not ECS code, you can get a reference to your default ECS World's EntityManager. From there, you can set an IComponentData with a bool value to true. This signals to an ECS System that the player has pressed the button. The ECS System then does some work, and reverts the bool back to false at the end.

    This is very similar to a callback from the button. The key is that the alert has to come from the data being changed, and the System Query being properly set up to detect the change in that Entity's Component data.
     
  4. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,058
    I've made a different implementation using the asset guid as reference.
    I've seen some Scene referencing code which included a
    Hash128
    .
    Using an asset guid is easier to associate prefabs. No need for enum values or extra specific component tags.

    A prefab authoring baker which defines the Prefab and its asset guid.
    Code (csharp):
    1. public class PrefabAuthoring : MonoBehaviour
    2. {
    3.     public GameObject Prefab;
    4.     public string Guid;
    5.  
    6. #if UNITY_EDITOR
    7.     public void OnValidate()
    8.     {
    9.         var path = UnityEditor.AssetDatabase.GetAssetPath(Prefab);
    10.         if (string.IsNullOrEmpty(path))
    11.             return;
    12.  
    13.         var guid = UnityEditor.AssetDatabase.GUIDFromAssetPath(path);
    14.         Guid = guid.Empty() ? null : guid.ToString();
    15.         UnityEditor.EditorUtility.SetDirty(this);
    16.     }
    17. #endif
    18. }
    19.  
    20. public class PrefabAuthoringBaker : Baker<PrefabAuthoring>
    21. {
    22.     public override void Bake(PrefabAuthoring authoring)
    23.     {
    24.         if (authoring.Prefab == null) return;
    25.        
    26.         var prefabEntity = GetEntity(authoring.Prefab);
    27.         AddComponent(new PrefabReference
    28.         {
    29.             Prefab = prefabEntity,
    30.             Guid = new Unity.Entities.Hash128(authoring.Guid)
    31.         });
    32.     }
    33. }
    34.  
    35. public struct PrefabReference : IComponentData
    36. {
    37.     public Entity Prefab;
    38.     public Hash128 Guid;
    39.    
    40.     public bool Compare(string value)
    41.     {
    42.         return Guid == new Hash128(value);
    43.     }
    44. }

    I kept the
    PrefabSpawnRequest
    which is created on the UI side after querying for the prefab.
    But it could be anywhere else as long as you can query for the prefab with a given asset guid.
    Code (csharp):
    1. public struct PrefabSpawnRequest : IComponentData
    2. {
    3.     public Entity Prefab;
    4.     public int Amount;
    5. }

    On the UI side the clickable event, I query for the prefab with the guid. In this case a cube.
    Then I create a new entity with a
    PrefabSpawnRequest
    referencing to the queried prefab.
    For now I have an amount and I'm not providing any positions or whatever. I will iterate on that.
    I probably need to add native arrays for both position and rotation to the request data.
    Code (csharp):
    1. private void OnSpawnCubesButtonClicked()
    2. {
    3.     // Query the prefab
    4.     if (GetPrefabEntity(CubePrefabGUID, out var prefabEntity) && prefabEntity != Entity.Null)
    5.     {
    6.         // Instantiate the spawn request
    7.         var spawnRequestEntity = entityManager.CreateEntity(typeof(PrefabSpawnRequest));
    8.         var spawnRequest = new PrefabSpawnRequest
    9.         {
    10.             Prefab = prefabEntity,
    11.             Amount = amountField.value
    12.         };
    13.         entityManager.SetComponentData(spawnRequestEntity, spawnRequest);
    14.         Debug.Log($"Spawn request sent for {spawnRequest.Prefab} x {spawnRequest.Amount}");
    15.     }
    16.     else Debug.Log("Cannot spawn prefab");
    17. }
    18.  
    19. private bool GetPrefabEntity(string prefabGUID, out Entity prefabEntity)
    20. {
    21.     var query = entityManager.CreateEntityQuery(typeof(PrefabReference));
    22.     using var prefabReferences = query.ToEntityArray(Allocator.Temp);
    23.  
    24.     for (var i = 0; i < prefabReferences.Length; i++)
    25.     {
    26.         var entity = prefabReferences[i];
    27.         var prefabReference = entityManager.GetComponentData<PrefabReference>(entity);
    28.  
    29.         if (!prefabReference.Compare(prefabGUID)) continue;
    30.  
    31.         prefabEntity = prefabReference.Prefab;
    32.         return true;
    33.     }
    34.  
    35.     Debug.LogWarning("Couldn't get prefab entity");
    36.     prefabEntity = Entity.Null;
    37.     return false;
    38. }

    Then for the system handling these prefab instantiation requests
    Code (csharp):
    1. [BurstCompile]
    2. public partial struct PrefabSpawnSystem : ISystem
    3. {
    4.     [BurstCompile]
    5.     public void OnCreate(ref SystemState state)
    6.     {
    7.         state.RequireForUpdate<PrefabSpawnRequest>();
    8.     }
    9.  
    10.     [BurstCompile]
    11.     public void OnDestroy(ref SystemState state)
    12.     {
    13.     }
    14.  
    15.     [BurstCompile]
    16.     public void OnUpdate(ref SystemState state)
    17.     {
    18.         // Spawn Job
    19.         var job = new HandleSpawnRequests
    20.         {
    21.             Ecb = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
    22.         };
    23.        
    24.         job.ScheduleParallel();
    25.     }
    26. }
    27.  
    28. [WithAll(typeof(PrefabSpawnRequest))]
    29. [BurstCompile]
    30. public partial struct HandleSpawnRequests : IJobEntity
    31. {
    32.     public EntityCommandBuffer.ParallelWriter Ecb;
    33.  
    34.     public void Execute([ChunkIndexInQuery] int chunkIndex, Entity entity, ref PrefabSpawnRequest request)
    35.     {
    36.         var entities = new NativeArray<Entity>(request.Amount, Allocator.Temp);
    37.         Ecb.Instantiate(chunkIndex, request.Prefab, entities);
    38.        
    39.         Ecb.DestroyEntity(chunkIndex, entity);
    40.     }
    41. }