├── README.md ├── UnitV2_Component.cs ├── UnitV2_Component.cs.meta ├── Unit_Buffer.cs ├── Unit_Buffer.cs.meta ├── Unit_Movement_System.cs ├── Unit_Movement_System.cs.meta ├── Unit_Spawner_Base.cs └── Unit_Spawner_Base.cs.meta /README.md: -------------------------------------------------------------------------------- 1 | # Unity-DOTS_Collision_Avoidance_System 2 | Source code for Unity DOTS based Collision Avoidance System - https://youtu.be/pGCVO1FV7PU 3 | 4 | UnitV2_Component - The componentData struct which holds the necessary data for every unit. 5 | Unit_Spawner_Base - A Monobehaviour class spawning entity with necessary components. Any code block with a comment - Debug - can be commented to increase performance. 6 | Unit_Movement_System - A system base operating on UnitV2_Component, responsible for movement and collision avoidance. Two methods demonstrated in this video exist as separate foreach loops on entities. 7 | Unit_Buffer - A dynamic buffer holding two locations between which units traverse. 8 | -------------------------------------------------------------------------------- /UnitV2_Component.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Mathematics; 3 | 4 | public struct UnitV2_Component : IComponentData 5 | { 6 | public float3 toLocation; 7 | public float3 fromLocation; 8 | public bool routed; 9 | public bool reached; 10 | //Movement 11 | public float3 waypointDirection; 12 | public float speed; 13 | public float minDistanceReached; 14 | public int currentBufferIndex; 15 | public int rotationSpeed; 16 | //Collision Avoidance 17 | public float3 avoidanceDirection; 18 | //Debug 19 | public bool collided; 20 | public int hashKey; 21 | public float timeStamp; 22 | } 23 | -------------------------------------------------------------------------------- /UnitV2_Component.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 06b0620d8d3f22248aae216e0280eda1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Unit_Buffer.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Mathematics; 3 | 4 | public struct Unit_Buffer : IBufferElementData 5 | { 6 | public float3 wayPoints; 7 | } 8 | -------------------------------------------------------------------------------- /Unit_Buffer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6c7ec60873bf9194a8aa94594666bc42 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Unit_Movement_System.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Jobs; 3 | using Unity.Mathematics; 4 | using Unity.Transforms; 5 | using Unity.Collections; 6 | 7 | public class Unit_Movement_System : SystemBase 8 | { 9 | public static NativeMultiHashMap cellVsEntityPositions; 10 | public static int totalCollisions; 11 | 12 | protected override void OnCreate() 13 | { 14 | totalCollisions = 0; 15 | cellVsEntityPositions = new NativeMultiHashMap(0, Allocator.Persistent); 16 | } 17 | 18 | public static int GetUniqueKeyForPosition(float3 position, int cellSize) 19 | { 20 | return (int)(19 * math.floor(position.x / cellSize) + (17 * math.floor(position.z / cellSize))); 21 | } 22 | 23 | protected override void OnUpdate() 24 | { 25 | float deltaTime = Time.DeltaTime; 26 | 27 | EntityQuery eq = GetEntityQuery(typeof(UnitV2_Component)); 28 | cellVsEntityPositions.Clear(); 29 | if (eq.CalculateEntityCount() > cellVsEntityPositions.Capacity) 30 | { 31 | cellVsEntityPositions.Capacity = eq.CalculateEntityCount(); 32 | } 33 | 34 | NativeMultiHashMap.ParallelWriter cellVsEntityPositionsParallel = cellVsEntityPositions.AsParallelWriter(); 35 | Entities 36 | .ForEach((ref UnitV2_Component uc, ref Translation trans) => 37 | { 38 | cellVsEntityPositionsParallel.Add(GetUniqueKeyForPosition(trans.Value, 25), trans.Value); 39 | }).ScheduleParallel(); 40 | 41 | //Resolve All Collisions in current cell and provide average avoidance direction 42 | /*NativeMultiHashMap cellVsEntityPositionsForJob = cellVsEntityPositions; 43 | Entities 44 | .WithReadOnly(cellVsEntityPositionsForJob) 45 | .ForEach((ref UnitV2_Component uc, ref Translation trans) => 46 | { 47 | int key = GetUniqueKeyForPosition(trans.Value, 25); 48 | NativeMultiHashMapIterator nmhKeyIterator; 49 | float3 currentLocationToCheck; 50 | float distanceThreshold = 1.5f; 51 | float currentDistance; 52 | int total = 0; 53 | uc.avoidanceDirection = float3.zero; 54 | if (cellVsEntityPositionsForJob.TryGetFirstValue(key, out currentLocationToCheck, out nmhKeyIterator)) 55 | { 56 | do 57 | { 58 | if (!trans.Value.Equals(currentLocationToCheck)) 59 | { 60 | currentDistance = math.sqrt(math.lengthsq(trans.Value - currentLocationToCheck)); 61 | if (currentDistance < distanceThreshold) 62 | { 63 | float3 distanceFromTo = trans.Value - currentLocationToCheck; 64 | uc.avoidanceDirection = uc.avoidanceDirection + math.normalize(distanceFromTo / currentDistance); 65 | total++; 66 | } 67 | //Debug 68 | if (math.sqrt(math.lengthsq(trans.Value - currentLocationToCheck)) < 0.05f) 69 | { 70 | uc.timeStamp = 60; 71 | uc.collided = true; 72 | } 73 | if (uc.collided) 74 | { 75 | uc.timeStamp = uc.timeStamp - deltaTime; 76 | } 77 | if (uc.timeStamp <= 0) 78 | { 79 | uc.collided = false; 80 | } 81 | } 82 | } while (cellVsEntityPositionsForJob.TryGetNextValue(out currentLocationToCheck, ref nmhKeyIterator)); 83 | if (total > 0) 84 | { 85 | uc.avoidanceDirection = uc.avoidanceDirection / total; 86 | } 87 | } 88 | }).ScheduleParallel();*/ 89 | 90 | //Resolve nearest collision 91 | NativeMultiHashMap cellVsEntityPositionsForJob = cellVsEntityPositions; 92 | Entities 93 | .WithReadOnly(cellVsEntityPositionsForJob) 94 | .ForEach((ref UnitV2_Component uc, ref Translation trans) => 95 | { 96 | int key = GetUniqueKeyForPosition(trans.Value, 25); 97 | NativeMultiHashMapIterator nmhKeyIterator; 98 | float3 currentLocationToCheck; 99 | float currentDistance = 1.5f; 100 | int total = 0; 101 | uc.avoidanceDirection = float3.zero; 102 | if (cellVsEntityPositionsForJob.TryGetFirstValue(key, out currentLocationToCheck, out nmhKeyIterator)) 103 | { 104 | do 105 | { 106 | if (!trans.Value.Equals(currentLocationToCheck)) 107 | { 108 | if (currentDistance > math.sqrt(math.lengthsq(trans.Value - currentLocationToCheck))) 109 | { 110 | currentDistance = math.sqrt(math.lengthsq(trans.Value - currentLocationToCheck)); 111 | float3 distanceFromTo = trans.Value - currentLocationToCheck; 112 | uc.avoidanceDirection = math.normalize(distanceFromTo / currentDistance); 113 | total++; 114 | } 115 | //Debug 116 | if (math.sqrt(math.lengthsq(trans.Value - currentLocationToCheck)) < 0.05f) 117 | { 118 | uc.timeStamp = 60; 119 | uc.collided = true; 120 | } 121 | if (uc.collided) 122 | { 123 | uc.timeStamp = uc.timeStamp - deltaTime; 124 | } 125 | if (uc.timeStamp <= 0) 126 | { 127 | uc.collided = false; 128 | } 129 | } 130 | } while (cellVsEntityPositionsForJob.TryGetNextValue(out currentLocationToCheck, ref nmhKeyIterator)); 131 | if (total > 0) 132 | { 133 | uc.avoidanceDirection = uc.avoidanceDirection / total; 134 | } 135 | } 136 | }).ScheduleParallel(); 137 | 138 | Entities 139 | .ForEach((ref UnitV2_Component uc, ref DynamicBuffer ub, ref Translation trans, ref Rotation rot) => 140 | { 141 | if (ub.Length > 0 && uc.routed) 142 | { 143 | uc.waypointDirection = math.normalize(ub[uc.currentBufferIndex].wayPoints - trans.Value); 144 | uc.waypointDirection = uc.waypointDirection + uc.avoidanceDirection; 145 | trans.Value += uc.waypointDirection * uc.speed * deltaTime; 146 | rot.Value = math.slerp(rot.Value, quaternion.LookRotation(uc.waypointDirection, math.up()), deltaTime * uc.rotationSpeed); 147 | if (!uc.reached && math.distance(trans.Value, ub[uc.currentBufferIndex].wayPoints) <= uc.minDistanceReached && uc.currentBufferIndex < ub.Length - 1) 148 | { 149 | uc.currentBufferIndex = uc.currentBufferIndex + 1; 150 | if (uc.currentBufferIndex == ub.Length - 1) 151 | { 152 | uc.reached = true; 153 | } 154 | } 155 | else if (uc.reached && math.distance(trans.Value, ub[uc.currentBufferIndex].wayPoints) <= uc.minDistanceReached && uc.currentBufferIndex > 0) 156 | { 157 | uc.currentBufferIndex = uc.currentBufferIndex - 1; 158 | if (uc.currentBufferIndex == 0) 159 | { 160 | uc.reached = false; 161 | } 162 | } 163 | } 164 | }).ScheduleParallel(); 165 | 166 | //Debug - HasKey 167 | /*Entities 168 | .ForEach((ref UnitV2_Component uc, ref Translation trans) => 169 | { 170 | uc.hashKey = GetUniqueKeyForPosition(trans.Value, 20); 171 | }).ScheduleParallel();*/ 172 | 173 | } 174 | 175 | protected override void OnDestroy() 176 | { 177 | cellVsEntityPositions.Dispose(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Unit_Movement_System.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0627498e7d44a97479c35aad500c513b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Unit_Spawner_Base.cs: -------------------------------------------------------------------------------- 1 | using Unity.Entities; 2 | using Unity.Mathematics; 3 | using Unity.Transforms; 4 | using UnityEngine; 5 | using Unity.Rendering; 6 | using System.Collections.Generic; 7 | 8 | public class Unit_Spawner_Base : MonoBehaviour 9 | { 10 | public int xGridCount; 11 | public int zGridCount; 12 | public Entity prefabToSpawn; 13 | public int destinationDistanceZAxis; 14 | public int minSpeed; 15 | public int maxSpeed; 16 | public float minDistanceReached; 17 | public int rotationSpeed; 18 | public float spawnGridEvery; 19 | public int maxUnitsToSpawn; 20 | public Mesh mesh; 21 | public Material material1; 22 | public Material material2; 23 | 24 | private float3[] allPositions; 25 | private EntityManager entitymamager; 26 | private EntityArchetype ea; 27 | private float elapsedTime; 28 | private float3 position; 29 | private float3 currentPosition; 30 | private List spawnedUnits; 31 | private int collisions; 32 | 33 | void Start() 34 | { 35 | collisions = 0; 36 | spawnedUnits = new List(); 37 | allPositions = new float3[xGridCount * zGridCount]; 38 | currentPosition = transform.position; 39 | entitymamager = World.DefaultGameObjectInjectionWorld.EntityManager; 40 | ea = entitymamager.CreateArchetype( 41 | typeof(Translation), 42 | typeof(Rotation), 43 | typeof(LocalToWorld), 44 | typeof(RenderMesh), 45 | typeof(RenderBounds), 46 | typeof(UnitV2_Component) 47 | ); 48 | int k = 0; 49 | for (int i = 1; i <= zGridCount; i++) 50 | { 51 | for (int j = 1; j <= xGridCount; j++) 52 | { 53 | allPositions[k] = new float3(currentPosition.x + j, currentPosition.y + 1, currentPosition.z + i); 54 | k++; 55 | } 56 | } 57 | } 58 | 59 | void Update() 60 | { 61 | elapsedTime += Time.deltaTime; 62 | if (elapsedTime > spawnGridEvery) 63 | { 64 | elapsedTime = 0; 65 | if (spawnedUnits.Count < maxUnitsToSpawn) 66 | { 67 | for (int h = 0; h <= allPositions.Length - 1; h++) 68 | { 69 | if (spawnedUnits.Count >= maxUnitsToSpawn) 70 | { 71 | break; 72 | } 73 | position = allPositions[h]; 74 | Entity e = entitymamager.CreateEntity(ea); 75 | entitymamager.AddComponentData(e, new Translation 76 | { 77 | Value = position 78 | }); 79 | entitymamager.AddComponentData(e, new Rotation 80 | { 81 | Value = Quaternion.identity 82 | }); 83 | entitymamager.AddComponentData(e, new UnitV2_Component 84 | { 85 | fromLocation = position, 86 | toLocation = new float3(0, 0, destinationDistanceZAxis) + position, 87 | currentBufferIndex = 0, 88 | speed = UnityEngine.Random.Range(minSpeed, maxSpeed), 89 | minDistanceReached = minDistanceReached, 90 | routed = true, 91 | rotationSpeed = rotationSpeed 92 | }); 93 | entitymamager.AddSharedComponentData(e, new RenderMesh 94 | { 95 | mesh = mesh, 96 | material = material2, 97 | castShadows = UnityEngine.Rendering.ShadowCastingMode.On 98 | }); 99 | DynamicBuffer ub = entitymamager.AddBuffer(e); 100 | ub.Add(new Unit_Buffer { wayPoints = new float3(position.x, position.y, (position.z + destinationDistanceZAxis)) }); 101 | ub.Add(new Unit_Buffer { wayPoints = position }); 102 | spawnedUnits.Add(e); 103 | } 104 | } 105 | } 106 | 107 | //Debug 108 | collisions = 0; 109 | foreach (Entity e in spawnedUnits) 110 | { 111 | RenderMesh r = entitymamager.GetSharedComponentData(e); 112 | UnitV2_Component uc2 = entitymamager.GetComponentData(e); 113 | if (uc2.reached == true) 114 | { 115 | r.material = material1; 116 | } 117 | else 118 | { 119 | r.material = material2; 120 | } 121 | if (uc2.collided) 122 | { 123 | collisions++; 124 | } 125 | entitymamager.SetSharedComponentData(e, r); 126 | } 127 | } 128 | 129 | private void OnGUI() 130 | { 131 | //GUI.Box(new Rect(10, 10, 500, 40), "Cell Size : " + 20); 132 | GUI.Box(new Rect(10, 10, 500, 40), "Units Spawned : " + spawnedUnits.Count); 133 | //GUI.Box(new Rect(10, 52, 500, 40), "Collisions Per Second: " + collisions / 2); //2 units colliding will be regarded as 1 collision 134 | GUI.skin.box.fontSize = 25; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Unit_Spawner_Base.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff9ae1354d16c9d48957c59bd791c782 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | --------------------------------------------------------------------------------