- Home /
 
 
               Question by 
               Hawaii_Dev · May 27 at 03:51 PM · 
                gameplaydots  
              
 
              How to organize interactions between entities on a grid? (ecs/dots)
I have an entity representing a plant. The entity has some components such as Grow, Age, GridCoord, Spread. When the Spread component timer reaches a threshold the plant should spread to nearby cells. The cells are 32 x 32 entities with their own logic.
How can I communicate between the Spread component and the cell at the Spread coordinate to mark that cell as "occupied" so that no more than one plant can occupy a single cell?
I have tried keeping a NativeArray of a component on the cells and changing that NativeArray at the correct index but that doesn't work as the entity connected to that component doesn't update.
 
               Comment
              
 
               
              Answer by andrew-lukasik · May 28 at 01:14 AM

 // src: https://gist.github.com/andrew-raphael-lukasik/19170443b976582f97404f6da9875b4c
 using UnityEngine;
 using Unity.Entities;
 using Unity.Mathematics;
 using Unity.Collections;
 using Unity.Jobs;
 
 namespace Sandbox
 {
     public class Planted : MonoBehaviour
     {
         [SerializeField] int _numPlantsOnStart = 5;
         [SerializeField] int2 _coordMinOnStart = -10, _coordMaxnOnStart = 10;
         EntityManager _entityManager;
         EntityQuery _query;
 
         void Start ()
         {
             var world = World.DefaultGameObjectInjectionWorld;
             _entityManager = world.EntityManager;
             _query = _entityManager.CreateEntityQuery( ComponentType.ReadOnly<Coord>() , ComponentType.ReadOnly<Spread>() , ComponentType.ReadOnly<Gene>() );
             var prefabs = world.GetOrCreateSystem<PrefabSystem>();
             var rnd = new Unity.Mathematics.Random( (uint) System.DateTime.Now.GetHashCode() );
             for( int i=0 ; i<_numPlantsOnStart ; i++ )
             {
                 Entity plantInstance = _entityManager.Instantiate( prefabs.Plant );
                 _entityManager.SetComponentData( plantInstance , new Coord{
                     Value = rnd.NextInt2( _coordMinOnStart , _coordMaxnOnStart )
                 } );
                 _entityManager.SetComponentData( plantInstance , new Age{
                     Value = rnd.NextFloat( 0f , rnd.NextFloat(Age.limit*0.1f) )
                 } );
 
                 var genes = _entityManager.GetBuffer<Gene>( plantInstance );
                 Color32 colorBytes = Color.HSVToRGB( rnd.NextFloat(1f) , 1f , 1f );
                 genes.Add( colorBytes.r );
                 genes.Add( colorBytes.g );
                 genes.Add( colorBytes.b );
             }
         }
         #if UNITY_EDITOR
         void OnDrawGizmos ()
         {
             if( !Application.isPlaying ) return;
             
             int len = _query.CalculateEntityCount();
             var entities = _query.ToEntityArray(Allocator.Temp).ToArray();
             var coords = _query.ToComponentDataArray<Coord>(Allocator.Temp).ToArray();
             for( int i=0 ; i<len ; i++ )
             {
                 var genes = _entityManager.GetBuffer<Gene>( entities[i] );
                 Gizmos.color = new Color32( genes[0] , genes[1] , genes[2] , 255 );
                 
                 int2 coord = coords[i].Value;
                 Gizmos.DrawSphere( new Vector3( coord.x , coord.y , 0 ) , 0.5f );
             }
         }
         #endif
     }
 
     public struct Coord : IComponentData
     {
         public int2 Value;
     }
     public struct Age : IComponentData
     {
         public float Value;
         public const float limit = 10f;
     }
     public struct Spread : IComponentData
     {
         public float Value;
         public const float spread_threshold = 2f;
     }
     [InternalBufferCapacity(5)]
     public struct Gene : IBufferElementData
     {
         public byte Value;
         public static implicit operator byte ( Gene value ) => value.Value;
         public static implicit operator Gene ( byte value ) => new Gene{ Value=value };
     }
 
     [UpdateInGroup( typeof(SimulationSystemGroup) )]
     public partial class SpreadSystem : SystemBase
     {
         EntityCommandBufferSystem _ecbSystem;
         GridSystem _gridSystem;
         PrefabSystem _prefabs;
         protected override void OnCreate ()
         {
             base.OnCreate();
             _ecbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
             _gridSystem = World.GetOrCreateSystem<GridSystem>();
             _prefabs = World.GetOrCreateSystem<PrefabSystem>();
         }
         protected override void OnUpdate ()
         {
             float deltaTime = Time.DeltaTime;
             uint rndHash = (uint) Time.ElapsedTime.GetHashCode();
             var grid = _gridSystem.Lookup;
             var plantPrefab = _prefabs.Plant;
             var geneBuffers = GetBufferFromEntity<Gene>( isReadOnly:false );
             var cmd = _ecbSystem.CreateCommandBuffer().AsParallelWriter();
 
             this.Dependency = JobHandle.CombineDependencies( Dependency , _gridSystem.LookupDependency );
 
             Entities
                 .WithReadOnly( grid )
                 .WithAll<Gene>()
                 .ForEach( ( ref Spread spread , in Coord coord , in Entity e , in int nativeThreadIndex ) =>
                 {
                     spread.Value += deltaTime;
 
                     if( spread.Value>Spread.spread_threshold )
                     {
                         var rnd = new Unity.Mathematics.Random( rndHash + (uint)e.GetHashCode() + (uint)nativeThreadIndex.GetHashCode() );
                         var myGenes = geneBuffers[e];
 
                         int2 spreadAttemptCoord = coord.Value + rnd.NextInt2( -2 , 2 );
                         if( !grid.ContainsKey(spreadAttemptCoord) )// is this spot free
                         {
                             var newPlant = cmd.Instantiate( nativeThreadIndex , plantPrefab );
                             cmd.SetComponent( nativeThreadIndex , newPlant , new Coord{ Value=spreadAttemptCoord } );
                             cmd.SetComponent( nativeThreadIndex , newPlant , new Spread{ Value=rnd.NextFloat(0f,Spread.spread_threshold*0.5f) } );
                             cmd.SetComponent( nativeThreadIndex , newPlant , new Age{ Value=rnd.NextFloat(0f,Age.limit*0.1f) } );
                             // propagate genes with slight mutation:
                             const byte mutation_range_half = 30;
                             cmd.AppendToBuffer<Gene>( nativeThreadIndex , newPlant , (byte)math.clamp( myGenes[0] + rnd.NextInt(-mutation_range_half,mutation_range_half+1) , 0 , 255 ) );
                             cmd.AppendToBuffer<Gene>( nativeThreadIndex , newPlant , (byte)math.clamp( myGenes[1] + rnd.NextInt(-mutation_range_half,mutation_range_half+1) , 0 , 255 ) );
                             cmd.AppendToBuffer<Gene>( nativeThreadIndex , newPlant , (byte)math.clamp( myGenes[2] + rnd.NextInt(-mutation_range_half,mutation_range_half+1) , 0 , 255 ) );
                         }
                         else// it's taken so lets attempt cross-pollination
                         {
                             foreach( var other in grid.GetValuesForKey(spreadAttemptCoord) )
                             if( geneBuffers.HasComponent(other) )
                             {
                                 var otherGenes = geneBuffers[other];
                                 otherGenes[0] = rnd.NextBool() ? otherGenes[0] : myGenes[0];
                                 otherGenes[1] = rnd.NextBool() ? otherGenes[1] : myGenes[1];
                                 otherGenes[2] = rnd.NextBool() ? otherGenes[2] : myGenes[2];
                             }
                         }
 
                         spread.Value = 0;
                     }
                 } )
                 .WithBurst()
                 .Schedule();
 
             _ecbSystem.AddJobHandleForProducer( Dependency );
         }
     }
 
     [UpdateInGroup( typeof(SimulationSystemGroup) )]
     public partial class AgeSystem : SystemBase
     {
         EntityCommandBufferSystem _ecbSystem;
         protected override void OnCreate ()
         {
             base.OnCreate();
             _ecbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
         }
         protected override void OnUpdate ()
         {
             float deltaTime = Time.DeltaTime;
             var cmd = _ecbSystem.CreateCommandBuffer().AsParallelWriter();
 
             Entities
                 .ForEach( ( ref Age age , in Entity e , in int nativeThreadIndex ) =>
                 {
                     age.Value += deltaTime;
                     
                     if( age.Value>Age.limit )
                         cmd.DestroyEntity( nativeThreadIndex , e );
                 } )
                 .WithBurst()
                 .ScheduleParallel();
             
             _ecbSystem.AddJobHandleForProducer( Dependency );
         }
     }
 
     [UpdateInGroup( typeof(SimulationSystemGroup) , OrderFirst=true )]
     public partial class GridSystem : SystemBase
     {
         /// <summary> Read-Only </summary>
         public NativeMultiHashMap<int2,Entity> Lookup { get; private set; }
         public JobHandle LookupDependency => base.Dependency;
         EntityCommandBufferSystem _ecbSystem;
         EntityQuery _queryNew;
         protected override void OnCreate ()
         {
             base.OnCreate();
             _ecbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
             _queryNew = EntityManager.CreateEntityQuery( new EntityQueryDesc{
                 None = new ComponentType[]{ ComponentType.ReadOnly<Tracked>() } ,
                 All = new ComponentType[]{ ComponentType.ReadOnly<Coord>() }
             } );
             Lookup = new NativeMultiHashMap<int2,Entity>( 1024 , Allocator.Persistent );
         }
         protected override void OnDestroy ()
         {
             base.OnDestroy();
             Lookup.Dispose();
         }
         protected override void OnUpdate ()
         {
             var cmd = _ecbSystem.CreateCommandBuffer().AsParallelWriter();
             var lookup = Lookup;
             var lookupPW = Lookup.AsParallelWriter();
 
             int countNewToLookup = _queryNew.CalculateEntityCount();
             if( countNewToLookup!=0 && (lookup.Count()+countNewToLookup)>lookup.Capacity/2 )
             {
                 lookup.Capacity = Mathf.Max( lookup.Count()+countNewToLookup , lookup.Capacity*2 );
                 Debug.Log($"{nameof(GridSystem)}.{nameof(Lookup)} capacity increased to {lookup.Capacity}");
             }
 
             Entities
                 .WithName("coord_created_job")
                 .WithNone<Tracked>()
                 .ForEach( ( in Coord coord , in Entity e , in int nativeThreadIndex ) =>
                 {
                     lookupPW.Add( coord.Value , e );
                     cmd.AddComponent<Tracked>( nativeThreadIndex , e , new Tracked{ Value=coord.Value } );
                 } )
                 .WithBurst()
                 .ScheduleParallel();
             
             Entities
                 .WithName("coord_destroyed_job")
                 .WithNone<Coord>()
                 .ForEach( ( in Tracked tracked , in Entity e , in int nativeThreadIndex ) =>
                 {
                     lookup.Remove( tracked.Value , e );
                     cmd.RemoveComponent<Tracked>( nativeThreadIndex , e );
                 } )
                 .WithBurst()
                 .Schedule();
             
             _ecbSystem.AddJobHandleForProducer( Dependency );
         }
         struct Tracked : ISystemStateComponentData { public int2 Value; }
     }
 
     [DisableAutoCreation]
     [UpdateInGroup( typeof(InitializationSystemGroup) )]
     public partial class PrefabSystem : SystemBase
     {
         public Entity Plant { get; private set; }
         protected override void OnCreate ()
         {
             base.OnCreate();
             this.Enabled = false;
             Plant = EntityManager.CreateEntity( typeof(Prefab) , typeof(Coord) , typeof(Age) , typeof(Spread) , typeof(Gene) );
         }
         protected override void OnUpdate () {}
     }
 
 }
 
                
                 
                gif-28052022-13-46-06v2.gif 
                (514.6 kB) 
               
 
              Your answer