算法简介
A*简单高效,可用于许多寻路算法中。
在这里我们共实现Node类,PriorityQueue类,NodeManager类和AStar类。
在Node类中,我们管理每个地图块的信息,包括是否为障碍、估值等。
PriorityQueue类用于储存处理完毕的和待处理的节点。
NodeManager类用来管理我们的Node类,在这里我们使用二维数组来管理。
AStar类则是我们的算法实现类。
步骤
- 将起始节点放置于OpenList中。
- 判断OpenList中是否还有元素,如有则继续。
- 从OpenList中拾取首个元素,使其成为当前节点,并获取它的相邻节点。
- 针对各个相邻节点,判断这些节点是否位于CloseList中,若否,则使用下列公式计算整体估值 (F) : F = G + H (其中G为起始节点到当前节点的整体估值,H为当前节点到目标节点的整体估值)
- 在相邻节点中储存这些估值并将当前节点设为相邻节点的父节点。
- 将相邻节点放入OpenList中并按照F进行排序。
- 将当前节点放入CloseList中并从OpenList中移除。
- 若当前节点不位于目标节点处则返回步骤2.。
算法实现
此处将按照顺序实现Node类、PriorityQueue类、NodeManager类及主类AStar类。
Node类
using System;
using UnityEngine;
using UnityEngine.UI;
public class Node : MonoBehaviour, IComparable
{
public float nodeTotalCost = 1.0f; //G
public float estimateCost = 0.0f; //F
public bool bObstacle;
public Node parent = null;
public void MarkAsObstacle()
{
bObstacle = true;
}
public int CompareTo(object obj)
{
Node node = (Node)obj;
if(estimateCost < node.estimateCost)
return -1;
else if(estimateCost > node.estimateCost)
return 1;
else
return 0;
}
public void ShowCost()
{
var text = GetComponentInChildren<Text>();
if (bObstacle)
{
text.text = "Obs";
text.color = Color.red;
}
else
text.text = "F: " + estimateCost + "\n" +
"G: " + nodeTotalCost + "\n";
}
}
Node类中有变量nodeTotalCost和estimateCost即前文提到的G(起始节点到此用的步数)和F(G+当前节点到目标节点的预估步数)。以及bObstacle用来标记这个节点是否为障碍物。parent则用来记录当前节点的上一个节点是什么。
在这里我们使用了CompareTo(object obj)这个方法并且继承了IComparable这个接口,这个是用来在把Node加入队列中进行比较排序使用的。微软官方文档,关于IComparable接口
PriorityQueue类
using System.Collections.Generic;
public class PriorityQueue
{
private List<Node> nodes = new List<Node>();
public int Count
{
get
{
return nodes.Count;
}
}
public bool Contains(Node node)
{
return nodes.Contains(node);
}
public Node First()
{
if(nodes.Count > 0)
{
return nodes[0];
}
return null;
}
public void Add(Node node)
{
nodes.Add(node);
nodes.Sort();
}
public void Remove(Node node)
{
nodes.Remove(node);
nodes.Sort();
}
}
构建后文要用的优先队列,可添加、删除元素,并在操作后对队列进行排序。可获取队列第一个元素,即最小值。以及获取队列中的元素总数和是否包含某元素。
NodeManager类
using System.Collections.Generic;
using UnityEngine;
public class NodeManager : MonoBehaviour
{
private static NodeManager instance = null;
public static NodeManager Instance
{
get
{
if (instance == null)
instance = FindObjectOfType(typeof(NodeManager)) as NodeManager;
if (instance == null)
Debug.LogError("Could not find a NodeManager. Please add one NodeManager in the scense");
return instance;
}
}
public const float X_START = -8.5f;
public const float Y_START = -3.0f;
private Node[,] nodes;
private int numOfColumns;
private int numOfRows;
private List<GameObject> obstacleList = new List<GameObject>();
private void SetColumn(int col)
{
if (col <= 2)
col = 2;
else if (col > 7)
col = 7;
numOfColumns = col;
}
private void SetRow(int row)
{
if (row <= 2)
row = 2;
else if (row > 18)
row = 18;
numOfRows = row;
}
public void SetObstacle(GameObject obs)
{
if (obstacleList.Contains(obs))
return;
obstacleList.Add(obs);
obs.GetComponent<Node>().MarkAsObstacle();
obs.GetComponent<Node>().ShowCost();
}
public void SetMap(int col, int row)
{
SetColumn(col);
SetRow(row);
nodes = new Node[numOfRows, numOfColumns];
for(int i = 0; i < numOfRows; i++)
{
for(int j = 0; j < numOfColumns; j++)
{
var n = Resources.Load<GameObject>("Node");
n = Instantiate(n) as GameObject;
n.transform.position = new Vector3(X_START + i, Y_START + j, 0);
Node thisNode = n.GetComponent<Node>();
nodes[i, j] = thisNode;
n.GetComponent<Node>().ShowCost();
}
}
}
//设置相邻节点,上下左右
public List<Node> GetNeighbours(Node node)
{
List<Node> neighbours = new List<Node>();
for (int i = 0; i < numOfRows; i++)
{
for (int j = 0; j < numOfColumns; j++)
{
if(nodes[i,j] == node)
{
neighbours = AssignNeighbours(i - 1, j, neighbours);//left
neighbours = AssignNeighbours(i + 1, j, neighbours);//right
neighbours = AssignNeighbours(i, j - 1, neighbours);//down
neighbours = AssignNeighbours(i, j + 1, neighbours);//up
}
}
}
return neighbours;
}
private List<Node> AssignNeighbours(int row, int col, List<Node> neighbours)
{
if (row >= 0 && col >= 0 && row < numOfRows && col < numOfColumns)
{
if(!obstacleList.Contains(nodes[row, col].gameObject))
{
neighbours.Add(nodes[row, col]);
}
}
return neighbours;
}
}
NodeManager这里使用了一个单例。这个类主要用于管理Node对象。包括设置地图、障碍物,以及设置某个节点的相邻节点。
AStar类
这个是主类,算法具体在这里实现。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AStar : MonoBehaviour
{
public static PriorityQueue openQueue, closeQueue;
private static float HeuristicEstimateCost(Node currentNode, Node goalNode)
{
return Vector3.Distance(currentNode.transform.position, goalNode.transform.position);
}
public static List<Node> FindPath(Node start, Node goal)
{
openQueue = new PriorityQueue();
openQueue.Add(start);
start.nodeTotalCost = 0;
start.estimateCost = HeuristicEstimateCost(start, goal);
closeQueue = new PriorityQueue();
Node node = null;
while (openQueue.Count != 0)
{
node = openQueue.First();
if(node == goal)
{
node.GetComponentInChildren<Text>().text = "goal";
break;
}
var neighbours = NodeManager.Instance.GetNeighbours(node);
foreach(Node neighbour in neighbours)
{
if(!closeQueue.Contains(neighbour))
{
neighbour.nodeTotalCost = node.nodeTotalCost + HeuristicEstimateCost(neighbour,node);
neighbour.estimateCost = HeuristicEstimateCost(neighbour, goal) + neighbour.nodeTotalCost;
neighbour.parent = node;
neighbour.ShowCost();
if (!openQueue.Contains(neighbour))
{
openQueue.Add(neighbour);
neighbour.GetComponentInChildren<Text>().color = Color.blue;
}
}
}
closeQueue.Add(node);
node.GetComponentInChildren<Text>().color = Color.green;
openQueue.Remove(node);
node.GetComponentInChildren<Text>().color = Color.black;
}
if (node != goal)
{
Debug.LogError("Cannot find Goal");
}
return CalculatePath(node);
}
private static List<Node> CalculatePath(Node node)
{
List<Node> path = new List<Node>();
while (node != null)
{
path.Add(node);
node = node.parent;
}
return path;
}
}
算法思想在前文。这里有openQueue和closeQueue。openQueue用于放置所有的已探明的节点及其相邻节点。closeQueue用于放置已探测过的节点。
结果显示
项目文件
来源:CSDN
作者:伊诺inno
链接:https://blog.csdn.net/qq_38015139/article/details/103909886