我遵循了 youtube 教程并确保我没有遗漏任何部分,但是当我运行项目时没有任何反应,我创建的单元对象应该请求通往目标的路径,然后使用 A* 遵循该路径,最烦人的部分是 unity 或 Visual Studio 都没有显示任何错误。
以下是我的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using System.IO;
using UnityEngine.AI;
using System;
public class Pathfinding : MonoBehaviour
{
//public Transform seeker, target;
PathRequestManager RequestManager;
Grid grid;
void Awake()
{
RequestManager = GetComponent<PathRequestManager>();
grid = GetComponent<Grid>();
}
//void Update()
//{
// findPath(seeker.position, target.position);
//}
public void StartFindPath(Vector3 startPos, Vector3 targetPos)
{
StartCoroutine(findPath(startPos, targetPos));
}
IEnumerator findPath (Vector3 startPos, Vector3 targetPos)
{
Stopwatch SW = new Stopwatch();
SW.Start();
Vector3[] waypoints = new Vector3[0];
bool pathSuccess = false;
Node startNode = grid.NodeFromWorldPoint(startPos);
Node targetNode = grid.NodeFromWorldPoint(targetPos);
if (startNode.walkable && targetNode.walkable)
{
Heap<Node> openSet = new Heap<Node>(grid.MaxSize);
HashSet<Node> closedSet = new HashSet<Node>();
openSet.Add(startNode);
while (openSet.Count > 0)
{
Node currentNode = openSet.RemoveFirst();
//replaced with line above for quicker speeds
//for (int i = 1; i < openSet.Count; i++)
// {
// if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost)
// {
// currentNode = openSet[i];
// }
// }
// openSet.Remove(currentNode);
closedSet.Add(currentNode);
if (currentNode == targetNode)
{
SW.Stop();
print("Path found:" + SW.ElapsedMilliseconds + "ms");
pathSuccess = true;
break;
}
foreach (Node neighbor in grid.GetNeighbors(currentNode))
{
if (!neighbor.walkable || closedSet.Contains(neighbor))
{
continue;
}
int newMovementCostToNeighbor = currentNode.gCost + GetDistance(currentNode, neighbor);
if (newMovementCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor))
{
neighbor.gCost = newMovementCostToNeighbor;
neighbor.hCost = GetDistance(neighbor, targetNode);
neighbor.parent = currentNode;
if (!openSet.Contains(neighbor))
openSet.Add(neighbor);
}
}
}
}
yield return null;
if (pathSuccess)
{
waypoints = RetracePath(startNode, targetNode);
}
RequestManager.FinishProcessingPath(waypoints, pathSuccess);
}
Vector3[] RetracePath (Node startNode, Node endNode)
{
List<Node> path = new List<Node>();
Node currentNode = endNode;
while (currentNode != startNode)
{
path.Add(currentNode);
currentNode = currentNode.parent;
}
Vector3[] waypoints = SimplifyPath(path);
Array.Reverse(waypoints);
return waypoints;
}
Vector3[] SimplifyPath(List<Node> path)
{
List<Vector3> waypoints = new List<Vector3>();
Vector2 directionOld = Vector2.zero;
for (int i =1 ; i < path.Count; i++)
{
Vector2 directionNew = new Vector2(path[i-1].GridX - path[i].GridX, path[i - 1].GridY - path[i].GridY);
if(directionNew!=directionOld)
{
waypoints.Add(path[i].worldPosition);
}
directionOld = directionNew;
}
return waypoints.ToArray();
}
int GetDistance(Node NodeA, Node NodeB)
{
int DstX = Mathf.Abs(NodeA.GridX - NodeB.GridX);
int DstY = Mathf.Abs(NodeA.GridY - NodeB.GridY);
if (DstX > DstY)
return 14*DstY + 10* (DstX - DstY);
return 14*DstX + 10* (DstY - DstX);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class PathRequestManager : MonoBehaviour
{
Queue<PathRequest> PathRequestQueue = new Queue<PathRequest>();
PathRequest currentPathRequest;
static PathRequestManager instance;
Pathfinding pathfinding;
bool isProcessingPath;
void Awake()
{
instance = this;
pathfinding = GetComponent<Pathfinding>();
}
public static void RequestPath(Vector3 pathStart, Vector3 pathEnd, Action<Vector3[], bool> callback)
{
PathRequest newRequest = new PathRequest(pathStart, pathEnd, callback);
instance.PathRequestQueue.Enqueue(newRequest);
instance.TryProcessNext();
}
void TryProcessNext()
{
if(!isProcessingPath && PathRequestQueue.Count > 0)
{
currentPathRequest = PathRequestQueue.Dequeue();
isProcessingPath = true;
pathfinding.StartFindPath(currentPathRequest.pathStart, currentPathRequest.pathEnd);
}
}
public void FinishProcessingPath(Vector3[] path, bool success)
{
currentPathRequest.callback(path, success);
isProcessingPath = false;
TryProcessNext();
}
struct PathRequest
{
public Vector3 pathStart;
public Vector3 pathEnd;
public Action<Vector3[], bool> callback;
public PathRequest(Vector3 _start, Vector3 _end, Action<Vector3[], bool> _callback)
{
pathStart = _start;
pathEnd = _end;
callback = _callback;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grid : MonoBehaviour
{
public bool DisplayGridGizmos;
public LayerMask unwalkableMask;
public Vector2 gridWorldSize;
public float nodeRadius;
Node[,] grid;
float nodeDiameter;
int gridSizeX, gridSizeY;
void Awake()
{
nodeDiameter = nodeRadius * 2;
gridSizeX = Mathf.RoundToInt(gridWorldSize.x/nodeDiameter);
gridSizeY = Mathf.RoundToInt(gridWorldSize.y/nodeDiameter);
CreateGrid();
}
public int MaxSize
{
get
{
return gridSizeX * gridSizeY;
}
}
void CreateGrid()
{
grid = new Node[gridSizeX, gridSizeY];
Vector3 worldBottomLeft = transform.position - Vector3.right * gridWorldSize.x / 2 - Vector3.forward * gridWorldSize.y / 2;
for (int x = 0; x < gridSizeX; x++)
{
for (int y = 0; y < gridSizeY; y++){
Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * nodeDiameter + nodeRadius) + Vector3.forward * (y * nodeDiameter + nodeRadius);
bool walkable = !(Physics.CheckSphere(worldPoint,nodeRadius,unwalkableMask));
grid[x,y] = new Node (walkable,worldPoint, x,y);
}
}
}
public List<Node> GetNeighbors(Node node)
{
List<Node> neighbors = new List<Node>();
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0)
continue;
int checkX = node.GridX + x;
int checkY = node.GridY + y;
if (checkX >=0 && checkX < gridSizeX && checkY >=0 && checkY < gridSizeY)
{
neighbors.Add(grid[checkX,checkY]);
}
}
}
return neighbors;
}
public Node NodeFromWorldPoint(Vector3 worldPosition)
{
float percentX = (worldPosition.x + gridWorldSize.x / 2) / gridWorldSize.x;
float percenty = (worldPosition.z + gridWorldSize.y / 2) / gridWorldSize.y;
percentX = Mathf.Clamp01(percentX);
percenty = Mathf.Clamp01(percenty);
int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
int y = Mathf.RoundToInt((gridSizeY - 1) * percenty);
return grid[x,y];
}
void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(gridWorldSize.x, 1, gridWorldSize.y));
if (grid != null && DisplayGridGizmos)
{
foreach (Node n in grid)
{
Gizmos.color = (n.walkable) ? Color.white : Color.red;
Gizmos.DrawCube(n.worldPosition, Vector3.one * (nodeDiameter - .1f));
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Unit : MonoBehaviour
{
public Transform target;
float speed = 20;
Vector3[] path;
int targetindex;
void start()
{
PathRequestManager.RequestPath(transform.position, target.position, OnPathFound);
}
public void OnPathFound(Vector3[] newPath, bool pathSuccessful)
{
if (pathSuccessful)
{
path = newPath;
StopCoroutine("FollowPath");
StartCoroutine("FollowPath");
}
}
IEnumerator FollowPath()
{
Vector3 currentWaypoint = path[0];
while(true)
{
if (transform.position == currentWaypoint)
{
targetindex ++;
if (targetindex >= path.Length)
{
targetindex = 0;
path = new Vector3[0];
yield break;
}
currentWaypoint = path[targetindex];
}
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint, speed * Time.deltaTime);
yield return null;
}
}
public void OnDrawGizmos()
{
if (path != null)
{
for (int i = targetindex; i < path.Length; i++)
{
Gizmos.color = Color.black;
Gizmos.DrawCube(path[i], Vector3.one);
if (i == targetindex)
{
Gizmos.DrawLine(transform.position, path[i]);
}
else
{
Gizmos.DrawLine(path[i - 1], path[i]);
}
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Node : IHeapItem<Node> {
public bool walkable;
public Vector3 worldPosition;
public int gCost;
public int hCost;
public int GridX;
public int GridY;
public Node parent;
int heapIndex;
public Node(bool _walkable, Vector3 _worldPos, int _Gridx, int _GridY)
{
walkable = _walkable;
worldPosition = _worldPos;
GridX = _Gridx;
GridY = _GridY;
}
public int fCost
{
get
{
return gCost + hCost;
}
}
public int HeapIndex
{
get { return heapIndex; }
set { heapIndex = value; }
}
public int CompareTo(Node nodeToComare)
{
int compare = fCost.CompareTo(nodeToComare.fCost);
if (compare == 0)
{
compare = hCost.CompareTo(nodeToComare.hCost);
}
return -compare;
}
}
using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
//Creating a heap to make the algorithm run faster
//A heap is a binary tree where each node has 2 child nodes
//Each parent node must have a lower f.cost than the child nodes
//If a new node is added and is a lower value then the parent the values swap until the new node can no longer swap
public class Heap<T> where T : IHeapItem<T>
{
T[] Items;
int currentItemCount;
public Heap(int maxHeapSize)
{
Items = new T[maxHeapSize];
}
public void Add(T item)
{
item.HeapIndex = currentItemCount;
Items[currentItemCount] = item;
SortUp(item);
currentItemCount++;
}
public T RemoveFirst()
{
T firstItem=Items[0];
currentItemCount--;
Items[0] = Items[currentItemCount];
Items[0].HeapIndex = 0;
SortDown(Items[0]);
return firstItem;
}
//update item position in the heap
public void UpdateItem(T item)
{
SortUp(item);
}
//get number of items in the heap
public int Count
{
get
{
return currentItemCount;
}
}
//check if heap contains a specific item
public bool Contains(T item)
{
return Equals(Items[item.HeapIndex], item);
}
void SortDown(T item)
{
while (true)
{
int childIndexLeft = item.HeapIndex * 2 + 1;
int childIndexRight = item.HeapIndex * 2 + 2;
int swapIndex = 0;
if (childIndexLeft < currentItemCount)
{
swapIndex = childIndexLeft;
if (childIndexRight < currentItemCount)
{
if (Items[childIndexLeft].CompareTo(Items[childIndexRight]) < 0)
{
swapIndex = childIndexRight;
}
}
//check if parent is lower priority than highest priority child
if (item.CompareTo(Items[swapIndex]) < 0)
{
Swap(item, Items[swapIndex]);
}
else
{
return;
}
}
else
{
return;
}
}
}
void SortUp(T item)
{
int parentIndex = (item.HeapIndex - 1) / 2;
while (true)
{
T parentItem = Items[parentIndex];
if (item.CompareTo(parentItem) > 0)
{
Swap (item, parentItem);
}
//when the item is no longer a higher priority than the parent item break the loop
else
{
break;
}
parentIndex = (item.HeapIndex - 1) / 2;
}
}
//method to swap child node values with parent
void Swap(T itemA, T itemB)
{
Items[itemA.HeapIndex] = itemB;
Items[itemB.HeapIndex] = itemA;
int itemAIndex = itemA.HeapIndex;
itemA.HeapIndex = itemB.HeapIndex;
itemB.HeapIndex = itemAIndex;
}
}
public interface IHeapItem<T> : IComparable<T>
{
int HeapIndex
{
get;
set;
}
}
如果有人能看到我一直想念的东西,请告诉我,这样我才能最终解决这个问题
我尝试查看脚本但什么也没看到,然后返回视频但仍然无法找出问题
所以首先,我想问题出在
Unit.cs
。您代码中的方法称为 start()
,它与 Unity 所需的方法 Start()
(区分大小写)不同。
更改
Unit
类中的代码:
public class Unit : MonoBehaviour
{
...
void Start()
{
PathRequestManager.RequestPath(transform.position, target.position, OnPathFound);
}
...
然后你的
PathRequestManager()
代码被调用。
在运行时,Unity 会查找您的 MonoBehavior 对象并通过反射执行它们。此过程区分大小写,因此所有特殊的 MonoBehavior 方法(如 Awake、OnEnable 等)必须与 Unity API 中描述的名称完全相同。
内置方法
Debug.Log()
是你最好的朋友。把它放在关键的地方,比如你的 PathRequestManager.cs
中的第 24 行,以确保有一条路径可以遵循。这将在运行时帮助您检查是否一切都在正确的时间正确调用。 👍