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