本文实现的A*算法,未经过大量的优化,后续文章会进一步实现优化
后篇:A*优化讨论
寻路代码实现
结点类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum E_Node_Type
{
/// <summary>
/// 可行走
/// </summary>
Normal,
/// <summary>
/// 障碍
/// </summary>
Obstacles
}
public class AStarNode
{
/// <summary>
/// x坐标
/// </summary>
public int x;
/// <summary>
/// y坐标
/// </summary>
public int y;
/// <summary>
/// 寻路消耗
/// </summary>
public float f;
/// <summary>
/// 距离起点距离
/// </summary>
public float g;
/// <summary>
/// 距离终点距离
/// </summary>
public float h;
/// <summary>
/// 父结点
/// </summary>
public AStarNode father;
/// <summary>
/// 结点类型
/// </summary>
public E_Node_Type type;
public AStarNode(int xPos, int yPos, E_Node_Type t)
{
this.x = xPos;
this.y = yPos;
this.type = t;
}
}
结点管理类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AStarManger : BaseManger<AStarManger>
{
public int mapW;
public int mapH;
private AStarNode startNode;
private AStarNode endNode;
//private Vector2 endVec = Vector2.right * -1;
public AStarNode[,] nodes;
List<AStarNode> openList = new List<AStarNode>();
List<AStarNode> closeList = new List<AStarNode>();
/// <summary>
/// 初始化
/// </summary>
public void InitAStarManger(int mapW, int mapH)
{
if(mapH < 0 || mapW < 0)
{
Debug.LogError("地图尺寸存在问题");
return;
}
this.mapW = mapW;
this.mapH = mapH;
nodes = new AStarNode[mapW, mapH];
for(int i = 0; i < this.mapW; i++)
{
for(int j = 0; j < this.mapH; j++)
{
AStarNode an = new AStarNode(i, j, Random.Range(0, 100) < 20 ? E_Node_Type.Obstacles : E_Node_Type.Normal);
nodes[i, j] = an;
}
}
}
/// <summary>
/// 开始寻径
/// </summary>
/// <returns></returns>
public List<AStarNode> FindPath(Vector2 startPos, Vector2 endPos)
{
if(startPos.x < 0 || startPos.y < 0 || endPos.x < 0 || endPos.y < 0
|| startPos.x >= this.mapW || startPos.y >= this.mapH
|| endPos.x >= this.mapW || endPos.y >= this.mapH)
{
Debug.LogError("坐标超出范围");
return null;
}
startNode = nodes[(int)startPos.x, (int)startPos.y];
endNode = nodes[(int)endPos.x, (int)endPos.y];
if(startNode.type is E_Node_Type.Obstacles || endNode.type is E_Node_Type.Obstacles)
{
Debug.LogError("起始结点或目标结点为障碍");
return null;
}
startNode.father = null;
startNode.f = 0;
startNode.g = 0;
startNode.h = 0;
openList.Clear();
closeList.Clear();
closeList.Add(startNode);
while(true)
{
//找附近点,并将符合条件的添加进openList
AddNearlyNode2OpenList(startNode.x - 1, startNode.y -1, 1.4f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x, startNode.y - 1, 1f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x + 1, startNode.y - 1, 1.4f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x - 1, startNode.y, 1f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x + 1, startNode.y, 1f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x - 1, startNode.y + 1, 1.4f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x, startNode.y + 1, 1f, startNode, endPos);
AddNearlyNode2OpenList(startNode.x + 1, startNode.y + 1, 1.4f, startNode, endPos);
if(openList.Count == 0)
{
Debug.LogWarning("终点不可达");
return null;
}
//排序
openList.Sort(SortRuler);
//选择新的父节点
startNode = openList[0];
//添加到closeList
closeList.Add(startNode);
openList.RemoveAt(0);
//判断是否到达终点
if(startNode == endNode)
{
List<AStarNode> path = new List<AStarNode>();
path.Add(endNode);
while(endNode.father != null)
{
path.Add(endNode.father);
endNode = endNode.father;
}
path.Reverse();
return path;
}
}
}
private int SortRuler(AStarNode a, AStarNode b)
{
if (a.f >= b.f) return 1;
else return -1;
}
/// <summary>
/// 将父结点周围符合条件结点,添加进openList
/// </summary>
private void AddNearlyNode2OpenList(int x, int y, float g, AStarNode father, Vector2 endPos)
{
//判断传入坐标是否超范围
if(x < 0 || y < 0 || x >= this.mapW || y >= this.mapH)
{
return;
}
AStarNode an = nodes[x, y];
//如果该结点是障碍结点 或 ol,cl里有,忽略
//如果结点为null,或是上下左右都有障碍物,忽略
if(an.type is E_Node_Type.Obstacles
|| an is null || closeList.Contains(an)
|| nodes[x, father.y].type is E_Node_Type.Obstacles
|| nodes[father.x, y].type is E_Node_Type.Obstacles)
{
return;
}
//单独判断是否在ol里,优化。如果结点已经在ol里,并且下一个最小f值的点也可以算到该点,那么该点的父节点不会变为这个最小值点,导致不是最短路径
if(openList.Contains(an))
{
float gThis = father.g + g;
if(gThis < an.g)
{
an.g = gThis;
an.f = an.g + an.h;
an.father = father;
}
return;
}
//父结点距离起点距离,加上自己距离父结点距离
an.g = father.g + g;
an.h = Mathf.Abs(endPos.x - x) + Mathf.Abs(endPos.y - y);
//计算曼哈顿距离
an.f = an.g + an.h;
//将结点加入openList
openList.Add(an);
//设置父结点
an.father = father;
}
}
单例模板:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseManger<T> where T : new()
{
private static T instance;
public static T Getinstance()
{
if(instance is null)
{
instance = new T();
}
return instance;
}
}
Unity中界面构建
测试脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class Test : MonoBehaviour
{
public float BeginX;
public float BeginY;
public float offsetX;
public float offsetY;
public int mapW;
public int mapH;
private Vector2 beginPos = Vector2.right * -1;
private Dictionary<string, GameObject> cubes = new Dictionary<string, GameObject>();
List<AStarNode> list = new List<AStarNode>();
private GameObject tmpStart;
private GameObject tmpEnd;
// Start is called before the first frame update
void Start()
{
Init();
}
// Update is called once per frame
void Update()
{
if(Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit info;
if(Physics.Raycast(ray, out info, 1000))
{
if(beginPos == Vector2.right * -1)
{
if (list != null)
{
for (int i = 0; i < list.Count; i++)
{
cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.white;
}
}
string[] strs = info.collider.gameObject.name.Split('_');
beginPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
if (tmpEnd) tmpEnd.GetComponent<MeshRenderer>().material.color = Color.white;
if (tmpStart) tmpStart.GetComponent<MeshRenderer>().material.color = Color.white;
tmpStart = info.collider.gameObject;
tmpStart.GetComponent<MeshRenderer>().material.color = Color.yellow;
}
else
{
string[] strs = info.collider.gameObject.name.Split('_');
Vector2 endPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
tmpEnd = info.collider.gameObject;
tmpEnd.GetComponent<MeshRenderer>().material.color = Color.blue;
list = AStarManger.Getinstance().FindPath(beginPos, endPos);
if(list != null)
{
for (int i = 0; i < list.Count; i++)
{
cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.green;
}
}
beginPos= Vector2.right * -1;
}
}
}
}
public void Init()
{
AStarManger.Getinstance().InitAStarManger(mapW, mapH);
tmpEnd = null;
tmpStart = null;
list = null;
if(cubes.Count != 0)
{
foreach (var obj in cubes)
{
Destroy(obj.Value.gameObject);
}
cubes.Clear();
}
for (int i = 0; i < mapW; i++)
{
for (int j = 0; j < mapH; j++)
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.position = new Vector3(BeginX + i * offsetX, BeginY + j * offsetY);
obj.transform.name = i + "_" + j;
cubes.Add(obj.name, obj);
AStarNode an = AStarManger.Getinstance().nodes[i, j];
if (an.type == E_Node_Type.Obstacles)
{
MeshRenderer mr = obj.GetComponent<MeshRenderer>();
mr.material.color = Color.red;
}
}
}
}
}
新建一个场景,将测试脚本挂载在任意物体上
新建一个画布,并添加一个按钮。其它ui元素可随意设定
将按钮关联Init方法
实现效果
正常终点可达:
终点不可达情况:
后续优化文章:
进一步优化文章来源:https://www.toymoban.com/news/detail-769394.html
本文仅为学习阶段的一个总结,按需浏览。存在一定不足的情况文章来源地址https://www.toymoban.com/news/detail-769394.html
到了这里,关于Unity 中的简单A*寻路 (AStar寻路)实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!