using GarmentButton.FlatenArray; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace GarmentButton.PathFinding { [BurstCompile] [UpdateAfter(typeof(DebugMouseHitSystem))] public partial struct CalculatePathSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); state.RequireForUpdate(); var queryGrid = SystemAPI.QueryBuilder().WithAll().WithNone().Build(); var query = SystemAPI.QueryBuilder().WithAll().Build(); state.RequireForUpdate(query); state.RequireForUpdate(queryGrid); } [BurstCompile] public void OnUpdate(ref SystemState state) { var gridData = SystemAPI.GetSingleton(); var gridAdditional = SystemAPI.GetSingleton(); BlobAssetReference grid = SystemAPI.GetSingleton().Value; var converter = new FlatenFloatArray2D(gridData.GridSize, gridData.CellSize, gridData.Center); var lenghtGrid = grid.Value.Array.Length; var ecb = SystemAPI.GetSingleton().CreateCommandBuffer(state.WorldUnmanaged); state.Dependency = new PathCalculationJob { Converter = converter, ECB = ecb.AsParallelWriter(), GroundTypes = gridAdditional.Types.AsReadOnly(), Grid = grid, LenghtGrid = lenghtGrid, }.ScheduleParallel(state.Dependency); } } [WithAll(typeof(EndPointPath))] [BurstCompile] public partial struct PathCalculationJob : IJobEntity { private const int MOVE_DISTANCE_STRIGHT = 10; private const int MOVE_DISTANCE_DAIGNOLE = 14; private const int MOVE_DISTANCE_HEIGHT = 10; private const int LIMIT_DISTANCE_HEIGHT = 35; private const int HOW_FAR_CAN_JUMP = 10; private const int START_SEARCH_DISTANCE = 10; private const ushort INVALID_END_POINT_INDEX = ushort.MaxValue; [ReadOnly] public BlobAssetReference Grid; [ReadOnly] public NativeArray.ReadOnly GroundTypes; [ReadOnly] public int LenghtGrid; [ReadOnly] public FlatenFloatArray2D Converter; public EntityCommandBuffer.ParallelWriter ECB; public void Execute(Entity entity, [ChunkIndexInQuery] int sortKey, ref DynamicBuffer buffer, ref EndPointIndexPath endPointIndex, in EndPointPath target, in LocalTransform starPosition) { buffer.Clear(); var gridPath = new NativeArray(LenghtGrid, Allocator.Temp); for (int i = 0; i < LenghtGrid; i++) { var point = Grid.Value.Array[i]; var data = new PathNode { Position = new int2(point.X, point.Z), Height = point.Y, CurrentIndex = i, GCost = int.MaxValue, CameFromIndex = -1 }; data.CalculateFCost(); gridPath[i] = data; } var destination = target.Position; var starIndex = Converter.GetCellAtPosition(starPosition.Position.xz); var destinationIndex = Converter.GetCellAtPosition(destination.xz); endPointIndex.Value = INVALID_END_POINT_INDEX; starIndex = GetNeighbourNodesForStart(starIndex, Converter, ((int)(starPosition.Position.y * 10)) + 1); var starPoint = gridPath[starIndex]; var endPoint = gridPath[destinationIndex]; starPoint.GCost = 0; starPoint.HCost = CalculateDistance(starPoint, endPoint); starPoint.CalculateFCost(); gridPath[starIndex] = starPoint; //Wanted To search var initialSearchCapacity = math.min(LenghtGrid, 128); var requestToSearch = new NativeList(initialSearchCapacity, Allocator.Temp); //Already Searched var searched = new NativeList(initialSearchCapacity, Allocator.Temp); var closed = new NativeArray(LenghtGrid, Allocator.Temp, NativeArrayOptions.ClearMemory); var openIndexes = new NativeArray(LenghtGrid, Allocator.Temp, NativeArrayOptions.ClearMemory); AddToOpen(ref requestToSearch, openIndexes, starPoint); var gridWidth = Converter.GridSize.x; var gridHeight = Converter.GridSize.y; var foundUsablePath = false; while (requestToSearch.Length > 0) { var currentNode = PopOpen(ref requestToSearch, openIndexes); if (closed[currentNode.CurrentIndex] != 0) continue; if (currentNode.CurrentIndex == endPoint.CurrentIndex) { CalculatePath(buffer, gridPath, currentNode, entity, sortKey); foundUsablePath = true; break; } AddToClosed(currentNode.CurrentIndex, closed, ref searched, currentNode); var currentGroundType = GroundTypes[currentNode.CurrentIndex]; var currentX = currentNode.CurrentIndex % gridWidth; var currentY = currentNode.CurrentIndex / gridWidth; for (var yOffset = -1; yOffset <= 1; yOffset++) { for (var xOffset = -1; xOffset <= 1; xOffset++) { if ((xOffset | yOffset) == 0) continue; var neighbourX = currentX + xOffset; var neighbourY = currentY + yOffset; if ((uint)neighbourX >= (uint)gridWidth || (uint)neighbourY >= (uint)gridHeight) continue; var index = neighbourY * gridWidth + neighbourX; var neighbourNode = gridPath[index]; var neighbourGroundType = GroundTypes[index]; if ((int)neighbourGroundType >= (int)GroundType.Blocked) { AddToClosed(index, closed, ref searched, neighbourNode); continue; } if (closed[index] != 0) continue; int layerBase = 0; switch (neighbourGroundType) { case GroundType.Air: if (currentGroundType == GroundType.Air && currentNode.CameFromIndex != -1) if (!IsSameDirection(gridPath[currentNode.CameFromIndex], currentNode, neighbourNode)) layerBase = 20; break; case GroundType.MoveAble: case GroundType.NearGround: layerBase = 15; break; } if (!CanJumpIt(currentNode, neighbourNode)) { continue; } int tentativeGCost = currentNode.GCost + CalculateDistance(currentNode, neighbourNode) + layerBase; if (tentativeGCost < neighbourNode.GCost) { neighbourNode.CameFromIndex = currentNode.CurrentIndex; neighbourNode.GCost = tentativeGCost; neighbourNode.HCost = CalculateDistance(neighbourNode, endPoint); neighbourNode.CalculateFCost(); gridPath[index] = neighbourNode; if (!CheckCanJumpThrough(index, gridPath)) { RemoveNodeFromOpen(ref requestToSearch, openIndexes, index); AddToClosed(index, closed, ref searched, neighbourNode); continue; } var indexOpenList = openIndexes[index] - 1; if (indexOpenList >= 0) { UpdateOpen(ref requestToSearch, openIndexes, indexOpenList, neighbourNode); continue; } AddToOpen(ref requestToSearch, openIndexes, neighbourNode); } } } } if (buffer.Length == 0) { foreach (var node in searched) { var closesAirPoint = GroundTypes[node.CurrentIndex] == GroundType.Air && node.CameFromIndex != -1; if (!closesAirPoint) continue; CalculatePath(buffer, gridPath, node, entity, sortKey); foundUsablePath = buffer.Length > 0; break; } } if (foundUsablePath) endPointIndex.Value = (ushort)destinationIndex; ECB.SetComponentEnabled(sortKey, entity, false); gridPath.Dispose(); requestToSearch.Dispose(); searched.Dispose(); closed.Dispose(); openIndexes.Dispose(); } private bool IsSameDirection(PathNode back, PathNode currentNode, PathNode neighbourNode) { var backDirection = back.Position - currentNode.Position; var frontDirection = currentNode.Position - neighbourNode.Position; return backDirection.x == frontDirection.x && backDirection.y == frontDirection.y; } private void AddToClosed(int index, NativeArray closed, ref NativeList searched, PathNode node) { if (closed[index] != 0) return; closed[index] = 1; searched.Add(node); } private void RemoveNodeFromOpen(ref NativeList openList, NativeArray openIndexes, int nodeIndex) { var openListIndex = openIndexes[nodeIndex] - 1; if (openListIndex < 0) return; RemoveFromOpen(ref openList, openIndexes, openListIndex); } private PathNode PopOpen(ref NativeList openList, NativeArray openIndexes) { var node = openList[0]; RemoveFromOpen(ref openList, openIndexes, 0); return node; } private void AddToOpen(ref NativeList openList, NativeArray openIndexes, PathNode node) { openList.Add(node); var openListIndex = openList.Length - 1; openIndexes[node.CurrentIndex] = openListIndex + 1; SiftOpenUp(ref openList, openIndexes, openListIndex); } private void UpdateOpen(ref NativeList openList, NativeArray openIndexes, int openListIndex, PathNode node) { openList[openListIndex] = node; openIndexes[node.CurrentIndex] = openListIndex + 1; SiftOpenUp(ref openList, openIndexes, openListIndex); } private void RemoveFromOpen(ref NativeList openList, NativeArray openIndexes, int openListIndex) { var removedNodeIndex = openList[openListIndex].CurrentIndex; var lastOpenIndex = openList.Length - 1; var movedNodeIndex = openList[lastOpenIndex].CurrentIndex; openIndexes[removedNodeIndex] = 0; openList[openListIndex] = openList[lastOpenIndex]; openList.RemoveAtSwapBack(lastOpenIndex); if (openListIndex < openList.Length) { openIndexes[movedNodeIndex] = openListIndex + 1; SiftOpenDown(ref openList, openIndexes, openListIndex); SiftOpenUp(ref openList, openIndexes, openListIndex); } } private void SiftOpenUp(ref NativeList openList, NativeArray openIndexes, int openListIndex) { while (openListIndex > 0) { var parentIndex = (openListIndex - 1) >> 1; if (HasHigherPriority(openList[parentIndex], openList[openListIndex])) break; SwapOpen(ref openList, openIndexes, parentIndex, openListIndex); openListIndex = parentIndex; } } private void SiftOpenDown(ref NativeList openList, NativeArray openIndexes, int openListIndex) { while (true) { var leftIndex = (openListIndex << 1) + 1; if (leftIndex >= openList.Length) break; var rightIndex = leftIndex + 1; var bestIndex = leftIndex; if (rightIndex < openList.Length && HasHigherPriority(openList[rightIndex], openList[leftIndex])) bestIndex = rightIndex; if (HasHigherPriority(openList[openListIndex], openList[bestIndex])) break; SwapOpen(ref openList, openIndexes, openListIndex, bestIndex); openListIndex = bestIndex; } } private void SwapOpen(ref NativeList openList, NativeArray openIndexes, int leftIndex, int rightIndex) { var temp = openList[leftIndex]; openList[leftIndex] = openList[rightIndex]; openList[rightIndex] = temp; openIndexes[openList[leftIndex].CurrentIndex] = leftIndex + 1; openIndexes[openList[rightIndex].CurrentIndex] = rightIndex + 1; } private bool HasHigherPriority(PathNode left, PathNode right) { if (left.FCost != right.FCost) return left.FCost < right.FCost; return left.HCost < right.HCost; } public void CalculatePath(DynamicBuffer buffer, NativeArray pathNodes, PathNode endNodes, Entity entity, int index) { var reversePath = new NativeList(40, Allocator.Temp); var currentNode = endNodes; var lastGroundType = GroundType.Ground; var currentDirection = int2.zero; var lastHeightDirection = 0f; while (currentNode.CameFromIndex != -1) { var direction = pathNodes[currentNode.CameFromIndex].Position - pathNodes[currentNode.CurrentIndex].Position; var heightDirection = pathNodes[currentNode.CameFromIndex].Height - pathNodes[currentNode.CurrentIndex].Height; var currentGround = GroundTypes[currentNode.CurrentIndex]; var nextGround = GroundTypes[currentNode.CameFromIndex]; var sameDirection = (currentDirection.x == direction.x && currentDirection.y == direction.y); var sameType = lastGroundType == currentGround; var isDifferentTypeNext = currentGround != nextGround; var sameHeightDirection = lastHeightDirection == heightDirection; if (sameDirection && sameType && !isDifferentTypeNext && sameHeightDirection) { // reversePath[reversePath.Length - 1] = currentNode.CurrentIndex; } else { reversePath.Add(currentNode.CurrentIndex); } lastHeightDirection = heightDirection; lastGroundType = currentGround; currentDirection = direction; currentNode = pathNodes[currentNode.CameFromIndex]; } for (int i = reversePath.Length - 1; i >= 0; i--) { buffer.Add(new PathElement { IndexGrid = (ushort)reversePath[i] }); } reversePath.Dispose(); #if UNITY_EDITOR && PATHFINDING_DEBUG var path = ECB.AddBuffer(index, entity); for (int i = 0; i < pathNodes.Length; i++) path.Add(new PointsData { Cost = pathNodes[i].GCost }); ECB.AddComponent(index, entity); #endif } private bool CheckCanJumpThrough(int index, NativeArray pathPoint) { var currentNode = pathPoint[index]; int count = 0; while (currentNode.CameFromIndex != -1 && GroundTypes[currentNode.CurrentIndex] == GroundType.Air) { if (count > HOW_FAR_CAN_JUMP) { return false; } currentNode = pathPoint[currentNode.CameFromIndex]; count++; } return true; } private bool IsJumpingToAir(PathNode current, PathNode next) => (GroundTypes[current.CurrentIndex] != GroundType.Air && GroundTypes[next.CurrentIndex] == GroundType.Air); private bool IsJumpingToGround(PathNode current, PathNode next) => (GroundTypes[current.CurrentIndex] == GroundType.Ground && GroundTypes[next.CurrentIndex] == GroundType.Ground); private bool CanJumpIt(PathNode current, PathNode next) { var isNeededJump = next.Height > current.Height; if (!isNeededJump) return true; var differenceHeight = next.Height - current.Height; var canReachHeight = differenceHeight < LIMIT_DISTANCE_HEIGHT; if (!canReachHeight) return false; var isHeightMoreThanOneBox = differenceHeight > 15f; if (!isHeightMoreThanOneBox) return true; return IsJumpingToGround(current, next); } private int GetNeighbourNodesForStart(int currentNodeIndex, FlatenFloatArray2D converter, int height) { if (Grid.Value.Array[currentNodeIndex].Y < height + 2) return currentNodeIndex; var gridWidth = converter.GridSize.x; var gridHeight = converter.GridSize.y; var originX = currentNodeIndex % gridWidth; var originY = currentNodeIndex / gridWidth; for (var distance = 1; distance <= START_SEARCH_DISTANCE; distance++) { for (var direction = 0; direction < 8; direction++) { var neighbourX = originX; var neighbourY = originY; switch ((Direction)direction) { case Direction.Up_Left: neighbourX -= distance; neighbourY += distance; break; case Direction.Up: neighbourY += distance; break; case Direction.Up_Right: neighbourX += distance; neighbourY += distance; break; case Direction.Down_Left: neighbourX -= distance; neighbourY -= distance; break; case Direction.Down: neighbourY -= distance; break; case Direction.Down_Right: neighbourX += distance; neighbourY -= distance; break; case Direction.Left: neighbourX -= distance; break; case Direction.Right: neighbourX += distance; break; } if ((uint)neighbourX >= (uint)gridWidth || (uint)neighbourY >= (uint)gridHeight) continue; var index = neighbourY * gridWidth + neighbourX; if (Grid.Value.Array[index].Y > height) continue; return index; } } return currentNodeIndex; } private int CalculateDistance(PathNode a, PathNode b) { int xDistance = math.abs(a.Position.x - b.Position.x); int yDistance = math.abs(a.Position.y - b.Position.y); int heightDistance = a.Height - b.Height; int remaining = math.abs(xDistance - yDistance); return MOVE_DISTANCE_DAIGNOLE * math.min(xDistance, yDistance) + MOVE_DISTANCE_STRIGHT * remaining + MOVE_DISTANCE_HEIGHT + heightDistance; } } }