Unity C#实现A*算法
- 算法原理
简单来说就是把地图拆成N*N个格子 以每个格子为最小移动单位。知道起始点和终点。从起点开始遍历周围的格子。计算出他们的行走代价(F=G+H);并加到待搜索列表中去。 然后不断的找待搜索列表中F代价最小的节点 找到最小F代价的节点加入到已搜索列表中。 再开始遍历他周围的格子更新代价值G,H和F。最终最后遍历到终点或者是待搜索列表没有数据 算法结束。
- F=G+H
F代表这个格子的总代价 这个值越小说明这个可能是当前最佳的路线格子。 G代表从起始格子到当前格子的行走消耗代价。 H代表当前格子到终点的行走消耗代价 一般计算方法为 曼哈顿距离(当前格子到终点的水平X距离 + 当前格子到终点的垂直Y的距离 即:curNode.gCost = Mathf.Abs(curNode.x - endNode.x) + Mathf.Abs(curNode.y - endNode.y)) 另一种可以走对角线的计算方法为 切比雪夫距离(定义水平和垂直距离移动一格消耗10,对角线方向移动一格消耗为 14 = 即根号2取的整 乘10 避免小数运算) int offsetX = Mathf.Abs(curNode.posX - endNode.posX); int offsetY = Mathf.Abs(curNode.poxY - endNode.poxY); int offsetDis = Mathf.Abs(curNode- offsetY); //差值 为水平距离 例如 offsetX = 3 offsetY =1 就是水平走2格 斜着1格子 int maxOffset = offsetX > offsetY ? offsetX : offsetY; //找最大 最大值减去插值认为是斜边距离 curNode.gCost = offsetDis * 10+ (maxOffset - offsetDis) * 14;
- 注意在遍历周围格子时 需要判断他是否是在待搜索列表 如果在 就需要尝试计算一下当前点到这个周围格子的点的 gCost 如果比他原来记录的要小 那么就要更新一下他的gCost 和他的来源节点 赋值为当前节点。 这样就相当于更新了最佳路径
具体细节之间上代码把
先看Node.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Node : MonoBehaviour
{
public int gCost = 0;
public int hCost = 0;
public int fCost = 0;
public int posX;
public int poxY;
public void FCost()
{
fCost = hCost + gCost;
}
public Node parentNode = null;
public bool isWall = false;
[ContextMenu("SetWall 设置为墙体")]
public void SetWall()
{
MapManager.Instance.SetNodeWall(this.gameObject);
}
public string toString()
{
return "x: " + posX + "|y: " + poxY;
}
}
MapManger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapManager : MonoBehaviour
{
public static MapManager _instance;
const int ONE_PATH_COST = 10;
const int ONE_INCLIEND_PATH_COST = 14;
[Header("地图高")]
public const int hight = 20;
[Header("地图宽")]
public const int widget = 20;
[Header("格子")]
public GameObject nodeGameObj = null;
[Header("墙 shader")]
public Material wallMaterial = null;
public Node[,] allNodes = new Node[hight, widget];
public static MapManager Instance
{
get {
return _instance;
}
}
public void Awake()
{
_instance = this;
}
public void Start()
{
CreateMap();
}
public void CreateMap()
{
for (int i = 0; i < widget; i++)
{
for(int j = 0; j < hight; j++)
{
GameObject nodeObj = GameObject.Instantiate<GameObject>(nodeGameObj);
Vector3 pos = new Vector3(i, j, 0);
nodeObj.transform.position = pos;
nodeObj.gameObject.name = i + "_" + j;
Node nodeComponent = nodeObj.AddComponent<Node>();
nodeComponent.posX = i;
nodeComponent.poxY = j;
allNodes[i,j] = nodeComponent;
}
}
}
public void InitMapCost()
{
for (int i = 0; i < widget; i++)
{
for (int j = 0; j < hight; j++)
{
allNodes[i, j].gCost = int.MaxValue;
allNodes[i, j].hCost = 0;
allNodes[i, j].parentNode = null;
allNodes[i, j].FCost();
}
}
}
public Node GetNode(int x,int y)
{
return allNodes[x, y];
}
public int CalculTowNodeDistance(Node startNode, Node endNode)
{
int offsetX = Mathf.Abs(startNode.posX - endNode.posX);
int offsetY = Mathf.Abs(startNode.poxY - endNode.poxY);
int offsetDis = Mathf.Abs(offsetX - offsetY);
int maxOffset = offsetX > offsetY ? offsetX : offsetY;
return offsetDis * ONE_PATH_COST + (maxOffset - offsetDis) * ONE_INCLIEND_PATH_COST;
}
public List<Node> GetNeighborNodeList(Node node)
{
List<Node> neighborNodeList = new List<Node>();
int curPox = node.posX;
int curPoy = node.poxY;
int[,] neighborIndexs = new int[,]
{
{0,1 },
{0,-1},
{-1,0 },
{1,0 },
{-1,1 },
{1,1 },
{-1,-1 },
{1,-1 },
};
for(int i = 0; i< 8; i++)
{
int offsetX = neighborIndexs[i,0];
int offsetY = neighborIndexs[i, 1];
int realIndexX = curPox + offsetX;
int realIndexY = curPoy + offsetY;
if(realIndexX < 0 || realIndexX >= widget)
{
continue;
}
if (realIndexY < 0 || realIndexY >= hight)
{
continue;
}
neighborNodeList.Add(GetNode(realIndexX, realIndexY));
}
return neighborNodeList;
}
public void SetNodeWall(GameObject obj)
{
Node nodeComponet = obj.GetComponent<Node>();
nodeComponet.isWall = true;
MeshRenderer mr = obj.GetComponent<MeshRenderer>();
mr.material = wallMaterial;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AStarFind : MonoBehaviour
{
public Material startMat;
public Material endMat;
public Material nomarlMat;
public Material wallMaterial;
public GameObject startObj;
public GameObject endObj;
public List<Node> findPathNodeList = null;
public List<Node> FindAStartNodePath(Node startNode, Node endNode)
{
List<Node> toSearchList = new List<Node>() { startNode };
List<Node> findedList = new List<Node>();
MapManager.Instance.InitMapCost();
Node curNode = null;
while(toSearchList.Count > 0)
{
if(curNode == null)
{
curNode = toSearchList[0];
toSearchList.Remove(curNode);
}else
{
toSearchList.Sort((a, b) =>
{
var cost = a.fCost - b.fCost;
return cost;
});
curNode = toSearchList[0];
toSearchList.Remove(curNode);
}
if(curNode.Equals(endNode))
{
Debug.Log("term tips 找到路径了!!!");
Node curParentNode = endNode.parentNode;
List<Node> getPathNodeList = new List<Node>();
while(curParentNode != null)
{
getPathNodeList.Add(curParentNode);
curParentNode = curParentNode.parentNode;
}
getPathNodeList.Reverse();
return getPathNodeList;
}
findedList.Add(curNode);
List <Node> neighborNodeList = MapManager.Instance.GetNeighborNodeList(curNode);
for(int i = 0; i < neighborNodeList.Count; i++)
{
Node curNeighborNode = neighborNodeList[i];
if(curNeighborNode.isWall)
{
continue;
}
if(findedList.Contains(curNeighborNode))
{
continue;
}
int curGCost = curNode.gCost + MapManager.Instance.CalculTowNodeDistance(curNode, curNeighborNode);
int curHCost = MapManager.Instance.CalculTowNodeDistance(curNeighborNode, endNode);
int curFCost = curGCost + curHCost;
if (!toSearchList.Contains(curNeighborNode))
{
curNeighborNode.gCost = curGCost;
curNeighborNode.hCost = curHCost;
curNeighborNode.FCost();
curNeighborNode.parentNode = curNode;
toSearchList.Add(curNeighborNode);
}
else
{
if (curGCost < curNeighborNode.gCost)
{
curNeighborNode.gCost = curGCost;
curNeighborNode.hCost = curHCost;
curNeighborNode.FCost();
curNeighborNode.parentNode = curNode;
}
}
}
}
Debug.Log("term tips 找不到路径了 - -!");
return null;
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit raycastHit;
Physics.Raycast(ray, out raycastHit, 1000);
if (raycastHit.collider)
{
GameObject hitObj = raycastHit.collider.gameObject;
if (startObj == null || startObj.Equals(hitObj))
{
startObj = hitObj;
startObj.GetComponent<MeshRenderer>().material = startMat;
}
else if (endObj == null)
{
endObj = hitObj;
endObj.GetComponent<MeshRenderer>().material = endMat;
Node startNode = startObj.GetComponent<Node>();
Node endNode = endObj.GetComponent<Node>();
findPathNodeList = FindAStartNodePath(startNode, endNode);
if (findPathNodeList != null)
{
foreach (var node in findPathNodeList)
{
node.GetComponent<MeshRenderer>().material = startMat;
Debug.Log("path :" + node.toString());
}
}
}
}
}
else if (Input.GetMouseButton(1))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit raycastHit;
Physics.Raycast(ray, out raycastHit, 1000);
if (raycastHit.collider)
{
GameObject hitObj = raycastHit.collider.gameObject;
hitObj.GetComponent<Node>().isWall = true;
hitObj.GetComponent<MeshRenderer>().material = wallMaterial;
}
}
}
[ContextMenu("ClearStartAndEndPos 清空")]
public void ClearStartAndEndPos()
{
if(startObj)
{
startObj.GetComponent<MeshRenderer>().material = nomarlMat;
startObj = null;
}
if (endObj)
{
endObj.GetComponent<MeshRenderer>().material = nomarlMat;
endObj = null;
}
if (findPathNodeList != null)
{
foreach (var node in findPathNodeList)
{
node.GetComponent<MeshRenderer>().material = nomarlMat;
}
findPathNodeList = null;
}
}
}
最终效果
找到路径了
没有路径的情况:
|