A*算法
A*算法,A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快
基础知识直接略过
重温了下服务器的寻路算法,现存代码一共两种,一种是A*方格网格寻路(弃用,原因有遗留bug),另一种是三角形网格寻路(现用,听师傅这个效率较高,待会研究)。
此文主要是A*寻路的实现。
由于客户端有比较形象的表现方式,笔者就把A*在Unity里做了实现,有时候在想到底是酒不好喝还是游戏不好玩,还是navmesh不好用。ps:一般情况,寻路是放到服务器上的,客户端只要傻傻的当个演员就好了。
码钳约定
- 基于网格(方格)
- 方格长度为1,间距为1
- A*基本函数 F=G+H,估价函数GetCost()使用的欧几里得距离(二维平面真实距离)而非曼哈顿距离,见GetCost()详情,估价函数可以有很多选择视情况而定。
码钳准备
- 路面方片
- 人物模型
- 目标方片
- 阻挡方片
-
以上除了人物模型(项目里的拿来用了)都是自己手拼的
码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
/// A*寻路简单实现
/// author by wc 2017/5/25
//点
public class Point
{
public int x;
public int y;
public Point(int x,int y) {
this.x = x;
this.y = y;
}
public bool Equals(Point point) {
return x == point.x && y == point.y;
}
}
//寻路节点
public class Node :IComparable
{
public Point point;
public Node parentNode;
public int sourceCost
{
get;
set;
}
private Node() { }
public Node(Point point) { this.point = point; }
public int destinationCost
{
get;
set;
}
public int getH{
get { return sourceCost + destinationCost; }
}
public int CompareTo(object obj)
{
Node node = (Node)obj;
if (getH < node.getH)
{
return -1;
}
else if (getH == node.getH)
{
return 0;
}else{
return 1;
}
}
public int GetCost(Node node)
{
int m = node.point.x - point.x;
int n = node.point.y - point.y;
return (int)Math.Sqrt(m * m + n * n);
}
public bool Equals(Node node)
{
return point.Equals(node.point);
}
}
//链表(用于初始化开放and关闭列表)
public class PathList : LinkedList<Node>
{
//开放列表专用Add
public void Add(Node node){
Enumerator it = this.GetEnumerator();
while(it.MoveNext()){
Node oldNode = it.Current;
if (node.CompareTo(oldNode) <= 0)
{
//链表插入操作
LinkedListNode<Node> priNode = Find(oldNode);
LinkedListNode<Node> curNode = AddBefore(priNode, node);
return;
}
}
AddLast(node);
}
public bool Contains(Node node)
{
Enumerator it = this.GetEnumerator();
while (it.MoveNext())
{
Node oldNode = it.Current;
if (oldNode.Equals(node))
{
return true;
}
}
return false;
}
}
//主类
public class APathFinder : MonoBehaviour
{
#region 初始化值必填
public GameObject pathPrefab; //普通路面
public GameObject playerPrefab; //玩家模型
public GameObject obstaclePrefab; //阻挡模型
public GameObject aimPrefab; // 目标模型
public int[] originPos; //玩家初始位置
public int[] destinationPos; //目标位置
public bool isMove = false;//为true则开始寻路
public int hitCount = 5; //碰撞体(模拟)个数
#endregion
private static int maxX = 10; //X最大值
private static int maxY = 10; //Y最大值
private Point[] hitData = null;//碰撞体信息
private PathList openList = new PathList(); //开放列表
private PathList closeList = new PathList(); //关闭列表
private static Quaternion pathRotation = Quaternion.Euler( new Vector3(90, 0, 0)); //路面旋转
private GameObject player; //玩家对象
private Vector3 curAimPos = Vector3.zero;//玩家当前寻路的目标点
private LinkedList<Node> path = new LinkedList<Node>();//FindPath的结果,路线!!
private Animator animitor;//状态机
private LineRenderer line;
private int lineCount = 0;
private int lineIndex = 0;
/// <summary>
/// 寻路函数
/// </summary>
/// <param name="origination"></param>
/// <param name="destination"></param>
/// <returns></returns>
private LinkedList<Node> FindPath(Point origination, Point destination)
{
try
{
Node firstNode = new Node(origination);
Node destiNode = new Node(destination);
firstNode.sourceCost = 0;
firstNode.destinationCost = firstNode.GetCost(destiNode);
openList.AddFirst(firstNode);
Node neighborNode = null;
while (openList.Count > 0)
{
LinkedListNode<Node> firstLinkedListNode = openList.First;
Node firstNodeTruth = firstLinkedListNode.Value;
openList.RemoveFirst();
if (firstNodeTruth.Equals(destiNode))
{
openList.Clear();
closeList.Clear();
return makePath(firstNodeTruth);
}
else
{
closeList.AddLast(firstNodeTruth);
List<Node> limitNodes = getLimitNodeList(firstNodeTruth);
if (limitNodes.Count > 0)
{
for (int i = 0; i < limitNodes.Count; i++)
{
neighborNode = limitNodes[i];
if (!openList.Contains(neighborNode) && !closeList.Contains(neighborNode) && !isHit(neighborNode))
{
neighborNode.sourceCost = firstNodeTruth.sourceCost + 1;
neighborNode.destinationCost = neighborNode.GetCost(destiNode);
neighborNode.parentNode = firstNodeTruth;
openList.Add(neighborNode);
}
}
}
}
}
}
catch (Exception e)
{
LogManager.Log("FindPath Error:" + e.StackTrace, LogType.Error);
}
return null;
}
/// <summary>
/// 获得周围点
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public List<Node> getLimitNodeList(Node node)
{
List<Node> limitNode = new List<Node>();
int x = node.point.x;
int y = node.point.y;
int prex = 0;
int prey = 0;
//1
prex = x - 1;
prey = y;
if (prex < 0 || prex >= maxX)
{
prex = x;
}
if (prex != x)
{
limitNode.Add(new Node(new Point(prex, prey)));
}
//2
prex = x + 1;
prey = y;
if (prex < 0 || prex >= maxX)
{
prex = x;
}
if (prex != x)
{
limitNode.Add(new Node(new Point(prex, prey)));
}
//3
prex = x;
prey = y - 1;
if (prey < 0 || prey >= maxY)
{
prey = y;
}
if (prey != y)
{
limitNode.Add(new Node(new Point(prex, prey)));
}
//4
prex = x;
prey = y + 1;
if (prey < 0 || prey >= maxY)
{
prey = y;
}
if (prey != y)
{
limitNode.Add(new Node(new Point(prex, prey)));
}
return limitNode;
}
/// <summary>
/// 检查碰撞,出界
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private bool isHit(Node node)
{
if (node.point.x < 0 || node.point.x > maxX || node.point.y < 0 || node.point.y > maxY)
{
return false;
}
for (int i = 0; i < hitData.Length; i++)
{
if (hitData[i] == null)
{
continue;
}
if (hitData[i].Equals(node.point))
{
return true;
}
}
return false;
}
/// <summary>
/// 生成路线
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private LinkedList<Node> makePath(Node node)
{
LinkedList<Node> path = new LinkedList<Node>();
while (node != null && node.parentNode != null)
{
path.AddFirst(node);
node = node.parentNode;
}
path.AddFirst(node);
return path;
}
/// <summary>
/// 以下是行为表现,代码丑陋^_^
/// </summary>
void Start() {
hitData = new Point[hitCount];
InitMapAndObjs();
path = FindPath(new Point(originPos[0], originPos[1]), new Point(destinationPos[0], destinationPos[1]));
line = player.GetComponent<LineRenderer>();
lineCount = path.Count;
line.SetVertexCount(lineCount);
}
void Update() {
if (isMove)
{
Move();
}
}
private void InitMapAndObjs() {
if (pathPrefab == null || playerPrefab == null || obstaclePrefab == null || aimPrefab == null)
{
return;
}
int hitCountPre = 0;
for (int i = 0; i < maxX; i++)
{
for (int j = 0; j < maxY; j++)
{
if (originPos[0] == i && originPos[1] == j)
{
player = Instantiate(playerPrefab, new Vector3(i, 0, j), Quaternion.identity) as GameObject;
Instantiate(obstaclePrefab, new Vector3(i, 0, j), pathRotation);
animitor = player.GetComponent<Animator>();
animitor.SetBool("run",false);
}
else if (destinationPos[0] == i && destinationPos[1] == j)
{
Instantiate(aimPrefab, new Vector3(i, 0, j), pathRotation);
}
else if ((i == 5 && j >= 1) || (i == 7 && j >= 3 && j < 7) || (j == 6 && i > 2 && i < 6) || (j == 1 && i >= 7 && i <= 8 ) && hitCountPre < hitCount)
{
Instantiate(obstaclePrefab, new Vector3(i, 0, j), pathRotation);
hitData[hitCountPre] = new Point(i,j);
hitCountPre++;
}
else
{
Instantiate(pathPrefab, new Vector3(i, 0, j), pathRotation);
}
}
}
curAimPos = player.transform.position;
}
private bool isArrive() {
if(player == null){
return false;
}
return player.transform.position == curAimPos;
}
private void Move() {
if(player != null){
if (!animitor.GetBool("run"))
{
animitor.SetBool("run", true);
}
if (!isArrive())
{
player.transform.LookAt(curAimPos);
player.transform.position = Vector3.MoveTowards(player.transform.position, curAimPos, Time.deltaTime * 3);
return;
}
if (path.First != null)
{
Node node = path.First.Value;
curAimPos = new Vector3(node.point.x, 0, node.point.y);
print(curAimPos);
path.RemoveFirst();
line.SetPosition(lineIndex, new Vector3(curAimPos.x, 0.5f, curAimPos.z));
lineIndex++;
}
else {
animitor.SetBool("run", false);
isMove = false;
}
}
}
}