Here are my scripts. I wrote them following the DOTS A* Pathfinding tutorial from CodeMonkey.
PathNode:
public struct PathNode {
public int x;
public int y;
public int index;
public int gCost;
public int hCost;
public int fCost;
public bool isWalkable;
public int cameFromNodeIndex;
public void CalculateFCost() {
fCost = gCost + hCost;
}
public void SetIsWalkable(bool isWalkable) {
this.isWalkable = isWalkable;
}
}
Pathfinding:
using UnityEngine;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;
public class Pathfinding : MonoBehaviour {
public float pathTimeInterval = 1f;
const int MOVE_STRAIGHT_COST = 10;
const int MOVE_DIAGONAL_COST = 14;
Grid grid;
public int findPathJobCount = 1;
public int2 startPosition = new int2(0, 0),
endPosition = new int2(5, 5);
float intervalTimer;
void Awake() {
grid = FindObjectOfType<Grid>();
}
void Update() {
intervalTimer += Time.deltaTime;
if (intervalTimer >= pathTimeInterval) {
DoJobShit();
intervalTimer = 0;
}
}
void DoJobShit() {
float startTime = Time.realtimeSinceStartup;
NativeArray<JobHandle> jobHandleArray = new NativeArray<JobHandle>(findPathJobCount, Allocator.TempJob);
for (int i = 0; i < findPathJobCount; i++) {
FindPathJob findPathJob = new FindPathJob {
_startPosition = startPosition,
_endPosition = endPosition,
_gridSize = grid.gridSize,
_pathNodeArray = grid.grid
};
if (i == 0) {
jobHandleArray[i] = findPathJob.Schedule();
} else {
jobHandleArray[i] = findPathJob.Schedule(jobHandleArray[i - 1]);
}
}
JobHandle.CompleteAll(jobHandleArray);
jobHandleArray.Dispose();
Debug.Log("Time: " + ((Time.realtimeSinceStartup - startTime) * 1000f));
}
[BurstCompile]
struct FindPathJob : IJob {
public int2 _startPosition, _endPosition, _gridSize;
public NativeArray<PathNode> _pathNodeArray;
public void Execute() {
for (int x = 0; x < _gridSize.x; x++) {
for (int y = 0; y < _gridSize.y; y++) {
PathNode pathNode = new PathNode();
pathNode.gCost = int.MaxValue;
pathNode.hCost = CalculateDistanceCost(new int2(x, y), _endPosition);
pathNode.CalculateFCost();
}
}
NativeArray<int2> neighbourOffsetArray = new NativeArray<int2>(8, Allocator.Temp);
neighbourOffsetArray[0] = new int2(-1, 0); // Left
neighbourOffsetArray[1] = new int2(+1, 0); // Right
neighbourOffsetArray[2] = new int2(0, +1); // Up
neighbourOffsetArray[3] = new int2(0, -1); // Down
neighbourOffsetArray[4] = new int2(-1, -1); // Left Down
neighbourOffsetArray[5] = new int2(-1, +1); // Left Up
neighbourOffsetArray[6] = new int2(+1, -1); // Right Down
neighbourOffsetArray[7] = new int2(+1, +1); // Right Up
int endNodeIndex = CalculateIndex(_endPosition.x, _endPosition.y, _gridSize.x);
PathNode startNode = _pathNodeArray[CalculateIndex(_startPosition.x, _startPosition.y, _gridSize.x)];
startNode.gCost = 0;
startNode.CalculateFCost();
_pathNodeArray[startNode.index] = startNode;
NativeList<int> openList = new NativeList<int>(Allocator.Temp);
NativeList<int> closedList = new NativeList<int>(Allocator.Temp);
openList.Add(startNode.index);
while (openList.Length > 0) {
int currentNodeIndex = GetLowestCostFNodeIndex(openList, _pathNodeArray);
PathNode currentNode = _pathNodeArray[currentNodeIndex];
if (currentNodeIndex == endNodeIndex) {
// Reached our destination!
break;
}
// Remove current node from Open List
for (int i = 0; i < openList.Length; i++) {
if (openList[i] == currentNodeIndex) {
openList.RemoveAtSwapBack(i);
break;
}
}
closedList.Add(currentNodeIndex);
for (int i = 0; i < neighbourOffsetArray.Length; i++) {
int2 neighbourOffset = neighbourOffsetArray[i];
int2 neighbourPosition = new int2(currentNode.x + neighbourOffset.x, currentNode.y + neighbourOffset.y);
if (!IsPositionInsideGrid(neighbourPosition, _gridSize)) {
// Neighbour not valid position
continue;
}
int neighbourNodeIndex = CalculateIndex(neighbourPosition.x, neighbourPosition.y, _gridSize.x);
if (closedList.Contains(neighbourNodeIndex)) {
// Already searched this node
continue;
}
PathNode neighbourNode = _pathNodeArray[neighbourNodeIndex];
if (!neighbourNode.isWalkable) {
// Not walkable
continue;
}
int2 currentNodePosition = new int2(currentNode.x, currentNode.y);
int tentativeGCost = currentNode.gCost + CalculateDistanceCost(currentNodePosition, neighbourPosition);
if (tentativeGCost < neighbourNode.gCost) {
neighbourNode.cameFromNodeIndex = currentNodeIndex;
neighbourNode.gCost = tentativeGCost;
neighbourNode.CalculateFCost();
_pathNodeArray[neighbourNodeIndex] = neighbourNode;
if (!openList.Contains(neighbourNode.index)) {
openList.Add(neighbourNode.index);
}
}
}
}
PathNode endNode = _pathNodeArray[endNodeIndex];
if (endNode.cameFromNodeIndex == -1) {
// Didn't find a path!
//Debug.Log("Didn't find a path!");
} else {
// Found a path
NativeList<int2> path = CalculatePath(_pathNodeArray, endNode);
/*
foreach (int2 pathPosition in path) {
Debug.Log(pathPosition);
}
*/
path.Dispose();
}
//_pathNodeArray.Dispose();
neighbourOffsetArray.Dispose();
openList.Dispose();
closedList.Dispose();
}
NativeList<int2> CalculatePath(NativeArray<PathNode> pathNodeArray, PathNode endNode) {
if (endNode.cameFromNodeIndex == -1) {
// Couldn't find a path!
return new NativeList<int2>(Allocator.Temp);
} else {
// Found a path
NativeList<int2> path = new NativeList<int2>(Allocator.Temp);
path.Add(new int2(endNode.x, endNode.y));
PathNode currentNode = endNode;
while (currentNode.cameFromNodeIndex != -1) {
PathNode cameFromNode = pathNodeArray[currentNode.cameFromNodeIndex];
path.Add(new int2(cameFromNode.x, cameFromNode.y));
currentNode = cameFromNode;
}
return path;
}
}
bool IsPositionInsideGrid(int2 gridPosition, int2 gridSize) {
return
gridPosition.x >= 0 &&
gridPosition.y >= 0 &&
gridPosition.x < gridSize.x &&
gridPosition.y < gridSize.y;
}
int CalculateIndex(int x, int y, int gridWidth) {
return x + y * gridWidth;
}
int CalculateDistanceCost(int2 aPosition, int2 bPosition) {
int xDistance = math.abs(aPosition.x - bPosition.x);
int yDistance = math.abs(aPosition.y - bPosition.y);
int remaining = math.abs(xDistance - yDistance);
return MOVE_DIAGONAL_COST * math.min(xDistance, yDistance) + MOVE_STRAIGHT_COST * remaining;
}
int GetLowestCostFNodeIndex(NativeList<int> openList, NativeArray<PathNode> pathNodeArray) {
PathNode lowestCostPathNode = pathNodeArray[openList[0]];
for (int i = 1; i < openList.Length; i++) {
PathNode testPathNode = pathNodeArray[openList[i]];
if (testPathNode.fCost < lowestCostPathNode.fCost) {
lowestCostPathNode = testPathNode;
}
}
return lowestCostPathNode.index;
}
}
}
And the Grid, where dispose is called:
using UnityEngine;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;
public class Grid : MonoBehaviour {
public NativeArray<PathNode> pathNodeArray, grid;
JobHandle jobHandle;
GenerateGridJob gridJob;
public int2 gridSize = new int2(10, 10);
void Awake() {
pathNodeArray = new NativeArray<PathNode>(gridSize.x * gridSize.y, Allocator.Persistent);
grid = new NativeArray<PathNode>(gridSize.x * gridSize.y, Allocator.Persistent);
GenerateGrid();
}
void Update() {
if (jobHandle.IsCompleted) {
jobHandle.Complete();
grid = gridJob._pathNodeArray;
}
}
void GenerateGrid() {
jobHandle = new JobHandle();
gridJob = new GenerateGridJob {
_gridSize = gridSize,
_pathNodeArray = pathNodeArray
};
jobHandle = gridJob.Schedule();
}
void OnDestroy() {
pathNodeArray.Dispose();
grid.Dispose();
}
[BurstCompile]
struct GenerateGridJob : IJob {
public int2 _gridSize;
public NativeArray<PathNode> _pathNodeArray;
public void Execute() {
for (int x = 0; x < _gridSize.x; x++) {
for (int y = 0; y < _gridSize.y; y++) {
PathNode pathNode = new PathNode();
pathNode.x = x;
pathNode.y = y;
pathNode.index = CalculateIndex(x, y, _gridSize.x);
//pathNode.gCost = int.MaxValue;
//pathNode.hCost = CalculateDistanceCost(new int2(x, y), _endPosition);
//pathNode.CalculateFCost();
//replace later
pathNode.isWalkable = true;
pathNode.cameFromNodeIndex = -1;
_pathNodeArray[pathNode.index] = pathNode;
}
}
/*
// Place Testing Walls
{
PathNode walkablePathNode = pathNodeArray[CalculateIndex(1, 0, gridSize.x)];
walkablePathNode.SetIsWalkable(false);
pathNodeArray[CalculateIndex(1, 0, gridSize.x)] = walkablePathNode;
walkablePathNode = pathNodeArray[CalculateIndex(1, 1, gridSize.x)];
walkablePathNode.SetIsWalkable(false);
pathNodeArray[CalculateIndex(1, 1, gridSize.x)] = walkablePathNode;
walkablePathNode = pathNodeArray[CalculateIndex(1, 2, gridSize.x)];
walkablePathNode.SetIsWalkable(false);
pathNodeArray[CalculateIndex(1, 2, gridSize.x)] = walkablePathNode;
}
*/
//_pathNodeArray.Dispose();
}
int CalculateIndex(int x, int y, int gridWidth) {
return x + y * gridWidth;
}
}
}