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 create something similar to Dictionaries for storing prefabs that entities can access?

Discussion in 'Entity Component System' started by Panos2, May 11, 2023.

  1. Panos2

    Panos2

    Joined:
    Sep 25, 2022
    Posts:
    9
    Hello everyone. I am creating a chunk and block system where a chunk has a 3D dimension of x, y, z and spawns a 1x1x1 meter blocks in each coordinate.


    In the following code, I have created a system that spawns a specific block prefab in the chunk until it fills it up. In this example, the prefab is imported from the Monobehaviur and then changes to Entity in the baking.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using Unity.Burst;
    4. using Unity.Collections;
    5. using Unity.Entities;
    6. using Unity.Mathematics;
    7. using Unity.Scenes;
    8. using Unity.Transforms;
    9. using UnityEngine;
    10.  
    11. namespace Chunk
    12. {
    13.     //GameObject mono behavaiour to input chunk dimensions and block Prefab
    14.     public class Chunk : MonoBehaviour
    15.     {
    16.         public int3 chunkSize;
    17.         public GameObject block;
    18.     }
    19.  
    20.     // Chunk Component Data
    21.     public struct ChunkProperties : IComponentData
    22.     {
    23.         public Vector3 chunkPosition;
    24.         public int3 chunkSize;
    25.         public Entity block;
    26.     }
    27.  
    28.     //Baking Data from mono behaviour to Entity
    29.     public class ChunkBaker : Baker<Chunk>
    30.     {
    31.        
    32.         public override void Bake(Chunk authoring)
    33.         {
    34.             Entity Chunkentity = GetEntity(authoring, TransformUsageFlags.None);
    35.             AddComponent(Chunkentity, new ChunkProperties
    36.             {
    37.                 chunkSize = authoring.chunkSize,
    38.                 block = GetEntity(authoring.block, TransformUsageFlags.Dynamic),
    39.                 chunkPosition = authoring.transform.position
    40.             });        
    41.         }      
    42.     }
    43.  
    44.     //Generating the chunk
    45.     [BurstCompile]
    46.     [UpdateInGroup(typeof(InitializationSystemGroup))]
    47.     public partial struct GenerateChunkSystem : ISystem
    48.     {
    49.         [BurstCompile]
    50.         public void OnCreate(ref SystemState state)
    51.         {
    52.             //On create make requirement for OnUpdate so no errors pop up from wrong execution order
    53.             state.RequireForUpdate<ChunkProperties>();
    54.         }
    55.         [BurstCompile]
    56.         public void OnUpdate(ref SystemState state)
    57.         {
    58.             //On Update do once
    59.             state.Enabled = false;
    60.            
    61.             EntityCommandBuffer GenerateChunkBlocksjob = new EntityCommandBuffer(Allocator.Temp);
    62.  
    63.             //Get chunk entity and size
    64.             foreach (ChunkProperties chunkProperties in SystemAPI.Query<ChunkProperties>())
    65.             {
    66.                 int x = chunkProperties.chunkSize.x;
    67.                 int y = chunkProperties.chunkSize.y;
    68.                 int z = chunkProperties.chunkSize.z;
    69.  
    70.  
    71.                 // Create Job to spawn all block to chunk
    72.                 for (int Xcoord = 0; Xcoord < x; Xcoord++)
    73.                 {
    74.                     for (int Ycoord = 0; Ycoord < y; Ycoord++)
    75.                     {
    76.                         for (int Zcoord = 0; Zcoord < z; Zcoord++)
    77.                         {
    78.                             Entity block = GenerateChunkBlocksjob.Instantiate(chunkProperties.block);
    79.                             //Sets correct position for each entity block that instantiates
    80.                             GenerateChunkBlocksjob.SetComponent(block, new LocalTransform
    81.                             {
    82.                                 Position = new float3(
    83.                                 chunkProperties.chunkPosition.x + Xcoord,
    84.                                 chunkProperties.chunkPosition.y + Ycoord,
    85.                                 chunkProperties.chunkPosition.z + Zcoord),
    86.  
    87.                                 Rotation = Quaternion.identity,
    88.  
    89.                                 Scale = 1f
    90.                             });
    91.                         }
    92.                     }
    93.                 }        
    94.             }
    95.             GenerateChunkBlocksjob.Playback(state.EntityManager);
    96.         }
    97.     }
    98. }
    I have set this code to instantiate 32x32x32 chunks of a prefab block. I was wondering how I could have a list of Prefab Blocks in DOTS and a good way of navigating it so I can easily control what Prefab gets instantiated. I OOP it was straightforward as I would use Addressables, Dictionaries, and Enums.

    Here is how I would do it in OOP
    In the following code, I am creating an Enum with all possible block states

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class BlockTypes
    6. {
    7.     public enum blockTypesEnum
    8.     {
    9.         Air = 0,
    10.         Grass = 1,
    11.         Dirt = 2,
    12.         Stone = 3,
    13.         Sand = 4
    14.     }
    15. }

    Here I create 2 Dictionaries. The first has an enum key that points to a game object and the second is the same but instead of storing the GameObject it holds the path of Addressable

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using static BlockTypes;
    5. public class BlockDictionaries
    6. {
    7.     public Dictionary<blockTypesEnum, GameObject> blocksDictionary = new Dictionary<blockTypesEnum, GameObject>();
    8.     public Dictionary<blockTypesEnum, string> blocksPathDictionary = new Dictionary<blockTypesEnum, string>();
    9. }

    And this a function that loads all assets to the dictionaries so later I can access the GameObject but inserting the enum as key in the dictionary

    Code (CSharp):
    1.  
    2.  private async void LoadBlockAssets()
    3.     {
    4.         ICollection<AddressableAssetEntry> entries = blockAssetGroup.entries;
    5.         int i = 0;
    6.         foreach (AddressableAssetEntry entry in entries)
    7.         {
    8.             AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(entry.address);
    9.             await handle.Task;
    10.             blockTypes.blocksDictionary.Add(handle.Result.GetComponent<BlockData>().GetBlockType(), handle.Result);
    11.             blockTypes.blocksPathDictionary.Add(handle.Result.GetComponent<BlockData>().GetBlockType(), AssetDatabase.GetAssetPath(handle.Result));
    12.             i++;
    13.         }
    14.         GenerateWold();
    15.     }
    16.  
    How can I achieve a similar concept in ECS? I am very confident that the way I am tackling this problem in DOP is wrong
     
  2. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Is your project pure ecs? If so, you should never use gameObjects at runtime. The recommended for general purpose to access data like configurations or default prefabs is to use a singleton. You can generate an entity with the Baker class that encapsulates all your Blocks:

    Code (CSharp):
    1. public class BlockAuthoring : MonoBehaviour
    2. {
    3.     //fields and Baker<Block> implementation
    4. }
    5.  
    6. public struct BlockConfigComponent : IBufferElementData
    7. {
    8.     public Entity entity;
    9. }
    10.  
    11. public class ChunkConfigAuthoring : MonoBehaviour
    12. {
    13.     [SerializeField] BlockAuthoring[] _blocks;
    14.  
    15.     class Baker : Baker<ChunkConfigAuthoring>
    16.     {
    17.         public override void Bake(ChunkConfigAuthoring authoring)
    18.         {
    19.             Entity entity = GetEntity(TransformUsageFlags.ManualOverride);
    20.  
    21.             //Since it's a fixed length array we could use other kind of component, but buffer is fine for our example.
    22.             DynamicBuffer<BlockConfigComponent> buffer = AddBuffer<BlockConfigComponent>(entity);
    23.  
    24.             //Add all the baked gameObjects (as entity prefabs) to the buffer
    25.             for(int i = 0; i < authoring._blocks.Length; i++)        
    26.                 buffer.Add(new BlockConfigComponent { entity = GetEntity(authoring._blocks[i].gameObject, TransformUsageFlags.None) } );        
    27.         }
    28.     }
    29. }
    30.  
    31. public partial struct ChunkSystem : ISystem
    32. {
    33.     void ISystem.OnCreate(ref SystemState state) => state.RequireForUpdate<BlockConfigComponent>();
    34.  
    35.     void ISystem.OnUpdate(ref SystemState state)
    36.     {
    37.         //gets the singleton buffer
    38.         DynamicBuffer<BlockConfigComponent> config = SystemAPI.GetSingletonBuffer<BlockConfigComponent>();
    39.  
    40.         //gets the entity prefab at index 2 and instantiate a clone of it.
    41.         //Like with gameObjects but with entities
    42.         state.EntityManager.Instantiate(config.ElementAt(2).entity);
    43.     }
    44. }
     
  3. Panos2

    Panos2

    Joined:
    Sep 25, 2022
    Posts:
    9
    Amazing! That was very helpful and did the job!

    Right now the array is created in the inspector. would there be a way to create it dynamically based on the block enum I am using?

    Code (CSharp):
    1. public class Block : MonoBehaviour
    2. {
    3.     public GameObject block;
    4.     public blockList blockType;
    5. }
    6.  
    7. //BlockData stores all data of a block
    8. public struct BlockData : IComponentData
    9. {
    10.     public Entity block;
    11.     public blockList blockType;
    12. }
    13.  
    14. //Baking block Data to entity
    15. public class ChunkBaker : Baker<Block>
    16. {
    17.  
    18.     public override void Bake(Block authoring)
    19.     {
    20.         Entity BlockEntity = GetEntity(authoring, TransformUsageFlags.None);
    21.         AddComponent(BlockEntity, new BlockData
    22.         {
    23.             block = BlockEntity,
    24.             blockType = authoring.blockType
    25.         });
    26.     }
    27. }
    Code (CSharp):
    1. public class BlockList
    2. {
    3.     public enum blockList
    4.     {
    5.         Air = 0,
    6.         Grass = 1,
    7.         Dirt = 2,
    8.         Stone = 3,
    9.         Sand = 4,
    10.     }
    11. }
    upload_2023-5-12_17-9-34.png

    Code (CSharp):
    1.     public partial struct GenerateChunkSystem : ISystem
    2.     {
    3.        
    4.         [BurstCompile]
    5.         public void OnCreate(ref SystemState state)
    6.         {
    7.             //On create make requirement for OnUpdate so no errors pop up from wrong execution order
    8.             state.RequireForUpdate<ChunkProperties>();
    9.             state.RequireForUpdate<BlocksCollection>();
    10.         }
    11.         [BurstCompile]
    12.         public void OnUpdate(ref SystemState state)
    13.         {
    14.             //On Update do once
    15.             state.Enabled = false;
    16.          
    17.             EntityCommandBuffer GenerateChunkBlocksjob = new EntityCommandBuffer(Allocator.Temp);
    18.             DynamicBuffer<BlocksCollection> blocksCollection = SystemAPI.GetSingletonBuffer<BlocksCollection>();
    19.  
    20.             //Get chunk entity and size
    21.             foreach (ChunkProperties chunkProperties in SystemAPI.Query<ChunkProperties>())
    22.             {
    23.                 int x = chunkProperties.chunkSize.x;
    24.                 int y = chunkProperties.chunkSize.y;
    25.                 int z = chunkProperties.chunkSize.z;
    26.  
    27.                 Entity block;
    28.                 // Create Job to spawn all block to chunk
    29.                 for (int Ycoord = 0; Ycoord < y; Ycoord++)
    30.                 {
    31.                     for (int Xcoord = 0; Xcoord < x; Xcoord++)
    32.                     {
    33.                         for (int Zcoord = 0; Zcoord < z; Zcoord++)
    34.                         {
    35.                             if (Ycoord <= 27)
    36.                             {
    37.                                 block = GenerateChunkBlocksjob.Instantiate(blocksCollection.ElementAt(blockList.Stone.GetHashCode()).entity); // Stone
    38.                             }
    39.                             else if (Ycoord <= 30)
    40.                             {
    41.                                 block = GenerateChunkBlocksjob.Instantiate(blocksCollection.ElementAt(blockList.Dirt.GetHashCode()).entity); // Dirt
    42.                             }
    43.                             else
    44.                             {
    45.                                 block = GenerateChunkBlocksjob.Instantiate(blocksCollection.ElementAt(blockList.Grass.GetHashCode()).entity); // Grass
    46.                             }
    47.  
    48.                             //Sets correct position for each entity block that instantiates
    49.                             GenerateChunkBlocksjob.SetComponent(block, new LocalTransform
    50.                             {
    51.                                 Position = new float3(
    52.                                 chunkProperties.chunkPosition.x + Xcoord,
    53.                                 chunkProperties.chunkPosition.y + Ycoord,
    54.                                 chunkProperties.chunkPosition.z + Zcoord),
    55.  
    56.                                 Rotation = Quaternion.identity,
    57.  
    58.                                 Scale = 1f
    59.                             });
    60.                         }
    61.                     }
    62.                 }        
    63.             }
    64.             //Actually creates entities
    65.             GenerateChunkBlocksjob.Playback(state.EntityManager);
    66.         }
    67.     }
    68. }
    If a mistake happens and the order of blocks in the inspector is not in the correct order in the enum it will spawn the wrong block as I am using the HashCode of the index in the enum. Is there a way to instead of passing all objects in the array in the inspector, to be created automatically based on the order of the enum?
     

    Attached Files: