题外话
- 去年暑假开始接触Unity,到现在(2019/06/23)已经快一年了,认为自己成长了很多,感觉以前写的代码很丑陋
- 我的考试周(
实际上连续了一个月)已经结束了,嘻嘻嘻 - 应一位在职美术的邀请,参与了某个游戏的制作,这个也是游戏的一个模块,同时也因为这个原因此篇博客只能提供解决方案和部分代码,给不了项目文件
- IGG 2019G星计划那个暑期实习,6月14号最终面了,结果需要等到7月5号,真是让人心急啊,当场出结果多爽啊,就算是没过心里也不用惦记着(
害怕7月5号那天,邮箱收到“很遗憾…”开头的邮件)
如果你点开这篇博客,关于A*的基础知识你一定已经知道了
如果不知道请点击 https://blog.csdn.net/hitwhylz/article/details/23089415
多人寻路中存在的问题
1.不做任何设置直接寻路,会导致重叠
2.把周围的点设置为障碍物,直接二次寻路会导致性能消耗过大,额外专门开个线程还好一点
解决办法
说的简单一点就是:等待+二次寻路
这里,我们把需要寻路的“角色”或“人物”称为 Role
具体思路:
1.为每一个Role调用A*实例,得到路径
2.设置CircleCastAll,如果此Role与其他Role产生“碰撞”,进入等待状态,等待计时
3.在等待状态中,对Role的前进方向投射CircleCast,如果前进方向没有其他Role阻拦,继续前进
4.等待计时超过一定限制,根据情况二次寻路,把阻挡的Role设置为“不可通过”(实际上相当于又回到了第一步)
5.如果已经到达目的地,可以根据需求进行排列阵型(小范围寻路)
效果图
在下图中我只点击了一次
此效果图并没有步骤五的效果,其实我觉得没写View层,从这几个点能看出个鬼,可能因为我不是策划?
关于优化
1. 使用二叉堆
什么是二叉堆?http://www.ijiandao.com/2b/baijia/168869.html
反例:
去年我初次接触A*的时候,用我魔改的红黑树存的节点,效率也有提高。
面试的时候我还说我的A*数据结构用的红黑树,但没想到有二叉堆这么合适的东西,可恶啊
2. 在二次寻路中缩小寻路范围
3. 二次寻路范围偏移
根据物体与目标的位置进行范围偏移,总结的话就是一张图,尽量合理利用寻路范围
如何做到呢?
这里有点A和点B,我们先求出两点的中点C, 向量Vector = (mapCenterPos-mapStartPos),也就是寻路范围中心点到寻路空间左下角的向量, 我们这次寻路的起点坐标=C+Vector,就可以达到下图中的效果,目的就是最大限度的利用寻路空间。
关于代码
我在CSDN写的第一篇博客就是Unity2D中A*寻路算法,当时初学,年轻不懂事,不知道优化,存储节点用的红黑树(虽然也有性能提升,但是没二叉堆合适),代码复用性也不好
因为项目原因不能提供多人寻路的代码,只提供了新版本A*的代码
其中包含我改好的二叉堆,如果想用的话只需要
MapGrids localMapGrids;
AStarWayFinding wayFind;
List<Vector2> wayList;
//初始化格子
localMapGrids = new MapGrids(mapSize, startPos, mapStep, layerMask);//地图大小,起点,格子大小,碰撞投射层级
wayFind = new AStarWayFinding();
//把路径存储到wayList中
wayList=wayFind.WayFinding(rolePos, targetPos, localMapGrids);
以下是A*寻路所用到的全部代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HeapSort
{
private OpenlistNode[] array;
private int currentIndex;
public HeapSort(int size)
{
array = new OpenlistNode[size];
currentIndex = 0;
}
public OpenlistNode[] getList()
{
return this.array;
}
public int getSize()
{
return this.currentIndex;
}
//插入
public void insertSort(OpenlistNode source)
{
array[currentIndex] = source;
tickedUp(currentIndex++);
}
private void tickedUp(int index)
{
int parentIndex = (index - 1) / 2;//父节点索引
OpenlistNode temp = array[index];
while (index > 0 && array[parentIndex].F1 > temp.F1)
{
array[index] = array[parentIndex];
index = parentIndex;
parentIndex = (parentIndex - 1) / 2;
}
array[index] = temp;
}
public OpenlistNode getMin()
{
OpenlistNode minNode = array[0];
array[0] = array[--currentIndex];
trickleDown(0);
return minNode;
}
private void trickleDown(int index)
{
OpenlistNode temp = array[index];
int minChildIndex;
while (index < currentIndex / 2)
{
int leftChildIndex = 2 * index + 1;
int rightChildIndex = leftChildIndex + 1;
//右子节点存在,且小于左子节点
if (rightChildIndex < currentIndex && array[leftChildIndex].F1 > array[rightChildIndex].F1)
{
minChildIndex = rightChildIndex;
}
else
{
minChildIndex = leftChildIndex;
}
if (temp.F1 <= array[minChildIndex].F1)
{
break;
}
array[index] = array[minChildIndex];
index = minChildIndex;
}
array[index] = temp;
}
}
public class OpenlistNode
{
private Grid grid;
private int G;
private int F;
private OpenlistNode parent;
private int currentX;
private int currentY;
public OpenlistNode(Grid grid, int g, int h, OpenlistNode parent)
{
this.grid = grid;
this.G = g;
this.F = h + g;
this.parent = parent;
}
public Grid Grid { get => grid; set => grid = value; }
public int G1 { get => G; set => G = value; }
public int F1 { get => F; set => F = value; }
public OpenlistNode Parent { get => parent; set => parent = value; }
public int CurrentX { get => currentX; set => currentX = value; }
public int CurrentY { get => currentY; set => currentY = value; }
}
public class Grid
{
private bool wall;
private Vector2 worldPos;
public Grid(Vector2 worldPos)
{
this.WorldPos = worldPos;
}
public bool Wall { get => wall; set => wall = value; }
public Vector2 WorldPos { get => worldPos; set => worldPos = value; }
}
//初始化和存储所有格子所用的类
public class MapGrids
{
private float mapSize;
private Vector2 startPos;
private Vector2 gridSize;
public List<List<Grid>> Grids;
public int xySize;
public Vector2 getStartPos()
{
return this.startPos;
}
public Vector2 getGridSize()
{
return this.gridSize;
}
public float getMapSize()
{
return this.mapSize;
}
public MapGrids(float mapSize, Vector2 startPos, Vector2 gridSize, LayerMask layerMask)
{
this.mapSize = mapSize;
this.startPos = startPos;
this.gridSize = gridSize;
Grids = new List<List<Grid>>();
xySize = Mathf.FloorToInt(this.mapSize / this.gridSize.x);
Vector2 direction = new Vector2(0.0f, 0.0f);
Vector2 xStep = new Vector2(this.gridSize.x, 0.0f);
Vector2 yStep = new Vector2(0.0f, this.gridSize.y);
Vector2 worldPosStep = this.gridSize * 0.5f;
for (int i = 0; i < xySize; i++)
{
Grids.Add(new List<Grid>());
for (int j = 0; j < xySize; j++)
{
Grids[i].Add(new Grid(
this.startPos + xStep * i + yStep * j + worldPosStep
));
Grids[i][j].Wall = Physics2D.BoxCast(Grids[i][j].WorldPos, this.gridSize, 0.0f, direction, layerMask);
}
}
}
}
public class AStarWayFinding
{
private int[] startGrid;
private int[] endGrid;
private OpenlistNode current;
private OpenlistNode pointer;
private int[] currentPosXY;
private HeapSort openList;
private HashSet<Grid> closeList;
private List<Vector2> way;
private int[] gridXY;
private bool isFirstTime;
private int searchOpenCount;
public List<Vector2> WayFinding(Vector2 startPos, Vector2 endPos, MapGrids mapGrids)
{
//初始化与计算坐标所在格子
way = new List<Vector2>();
currentPosXY = new int[2];
openList = new HeapSort(mapGrids.xySize*mapGrids.xySize);
closeList = new HashSet<Grid>();
startGrid = new int[2];
endGrid = new int[2];
gridXY = new int[2];
isFirstTime = true;
//如果不在界内
if (endPos.x - mapGrids.getStartPos().x < 1
|| endPos.y - mapGrids.getStartPos().y < 1
|| endPos.x - mapGrids.getStartPos().x - mapGrids.getMapSize() > -1
|| endPos.y - mapGrids.getStartPos().y - mapGrids.getMapSize() > -1
) return null;
startGrid[0] = Mathf.FloorToInt((startPos.x - mapGrids.getStartPos().x) / mapGrids.getGridSize().x);
startGrid[1] = Mathf.FloorToInt((startPos.y - mapGrids.getStartPos().y) / mapGrids.getGridSize().y);
endGrid[0] = Mathf.FloorToInt((endPos.x - mapGrids.getStartPos().x) / mapGrids.getGridSize().x);
endGrid[1] = Mathf.FloorToInt((endPos.y - mapGrids.getStartPos().y) / mapGrids.getGridSize().y);
//Debug.LogFormat("{0}{1}{2}{3}", startGrid[0], startGrid[1], endGrid[0], endGrid[1]);
if (Mathf.Abs(startGrid[0] - endGrid[0]) < 2 && Mathf.Abs(startGrid[1] - endGrid[1]) < 2)
{
if (!mapGrids.Grids[endGrid[0]][endGrid[1]].Wall) return new List<Vector2>();
return null;
}
//添加起点格子到OpenList
current = new OpenlistNode(mapGrids.Grids[startGrid[0]][startGrid[1]], 0, getDistance(startGrid, endGrid), null);
current.CurrentX = startGrid[0];
current.CurrentY = startGrid[1];
currentPosXY[0] = startGrid[0];
currentPosXY[1] = startGrid[1];
openList.insertSort(current);
//寻路过程
while (openList.getSize() != 0)
{
if (isFirstTime) openList.getMin();
isFirstTime = false;
closeList.Add(current.Grid);
if (currentPosXY[0] < 2 || currentPosXY[0] > mapGrids.xySize - 2 || currentPosXY[1] < 2 || currentPosXY[1] > mapGrids.xySize - 2)
{
if (openList.getSize() == 0) break;
current = openList.getMin();
currentPosXY[0] = current.CurrentX;
currentPosXY[1] = current.CurrentY;
}
else
{
if (aroundGrid(mapGrids))
{
getWay(mapGrids);
return way;
}
if (openList.getSize() == 0) break;
current = openList.getMin();
currentPosXY[0] = current.CurrentX;
currentPosXY[1] = current.CurrentY;
}
}
//-----------Debug.Log("寻路失败");
return null;
}
//获得路径
private void getWay(MapGrids mapGrids)
{
while (!(current.Parent.CurrentX == startGrid[0] && current.Parent.CurrentY == startGrid[1]))
{
way.Add(current.Grid.WorldPos);
current = current.Parent;
}
way.Reverse();
}
//遍历周围8个格子,如果周围存在目标那么寻路成功
private bool aroundGrid(MapGrids mapGrids)
{
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
if (i == 0 && j == 0) continue;
gridXY[0] = currentPosXY[0] + i;
gridXY[1] = currentPosXY[1] + j;
if (gridXY[0] == endGrid[0] && gridXY[1] == endGrid[1])
{
//-------------Debug.Log("寻路成功");
return true;
}
if (mapGrids.Grids[gridXY[0]][gridXY[1]].Wall || closeList.Contains(mapGrids.Grids[gridXY[0]][gridXY[1]]))
{
continue;
}
if (searchOpen(mapGrids.Grids[gridXY[0]][gridXY[1]]))
{
OpenlistNode opListNode = new OpenlistNode(mapGrids.Grids[gridXY[0]][gridXY[1]], current.G1 + getDistance(currentPosXY, gridXY), getDistance(gridXY, endGrid), current);
//Debug.LogFormat("{0},{1}", gridXY[0], gridXY[1]);
opListNode.CurrentX = gridXY[0];
opListNode.CurrentY = gridXY[1];
openList.insertSort(opListNode);
}
}
}
//Debug.Log("__________________");
return false;
}
private bool searchOpen(Grid grid)
{
searchOpenCount = 0;
foreach (OpenlistNode oln in openList.getList())
{
if (++searchOpenCount > openList.getSize()) return true;
if (grid == oln.Grid) return false;
}
return true;
}
//节点消耗计算
private int getDistance(int[] a, int[] b)
{
return Mathf.Abs(a[0] - b[0]) * 10 + Mathf.Abs(a[1] - b[1]) * 14;
}
}