六边形寻路
表现
(红色文本表示的是移动到该点的难度)
原理
本质还是A*的 f=g+h 那一套,就不细说了
与正方形寻路的区别
一.取周围能去的点不同
取正方形周围 4个(或者带斜向8个),改成了取六边形周围6个
二。排布不同
可以看到奇数列(0开头)的位置相对于正方形摆放来说,会稍微高一点。
如果把六边形转90°(横摆变竖摆),那就是奇数行会偏右
实现
根据索引确定六边形的坐标
简单的几何学.mp3
然后根据完全贴合摆放的偏移量来确定每个六边形的位置
public MyVector2 GetPos(int columnIndex, int rowIndex)
{
//横着摆,奇数行会错位
MyVector2 offset = new MyVector2();
if (columnIndex % 2 == 1)
offset.y = SQUARE_ROOT_OF_3 / 2f;
MyVector2 pos = new MyVector2()
{
x = columnIndex * 1.5f,
y = rowIndex * SQUARE_ROOT_OF_3,
};
pos += offset;//加六边形组合偏移
pos *= fNodeSize;//乘边长
pos += centerOffset;//加上地图中心点偏移
return pos;
}
取周围的六边形
终结索引的规律(竖摆就是把 Y 的偏移,换成了 X 的偏移)
public MyVector2Int[] GetRoundPos(MyVector2Int center)
{
List<MyVector2Int> tmp = new List<MyVector2Int>();
int offset = center.x % 2 == 0 ? -1 : 1;//偶数行上下的 x 是i-1,i。奇数行上下的 x 是 i,i+1
//左
tmp.Add(new MyVector2Int() { x = center.x - 1, y = center.y });
tmp.Add(new MyVector2Int() { x = center.x - 1, y = center.y + offset });
//上下
tmp.Add(new MyVector2Int() { x = center.x, y = center.y + 1 });
tmp.Add(new MyVector2Int() { x = center.x, y = center.y - 1 });
//右
tmp.Add(new MyVector2Int() { x = center.x + 1, y = center.y });
tmp.Add(new MyVector2Int() { x = center.x + 1, y = center.y + offset });
//去除越界的坐标
for (var i = 0; i < tmp.Count; i++)
{
if (tmp[i].x < 0 || tmp[i].x >= nColumnCount ||
tmp[i].y < 0 || tmp[i].y >= nRowCount)
{
tmp.RemoveAt(i);
i--;
}
}
return tmp.ToArray();
}
寻路算法
查找未检测点中的最优解,取 f 最小,也就 h+g 最小
var tmpList = listAll.FindAll((pathPoint) => !pathPoint.bHaveChecked)
.OrderBy((pathPoint) => pathPoint.g + pathPoint.h)
.ThenBy((pathPoint) => pathPoint.h).ToList();
检测还能不能找到下一个点
int maxNum = mapData.nColumnCount * mapData.nRowCount + 1;//防死循环
while (!sussecc)
{
...
if (tmpList == null || tmpList.Count == 0 || ++count > maxNum)
{
UnityEngine.Debug.Log($"{tmpList == null} - {tmpList.Count} - {count}");
//找不到下一个点,或者到不了终点就找离终点最近的点
lastPoint = listAll.OrderBy((pathPoint) => pathPoint.h).ToList()[0];
break;
}
...
}
计算当前最优点的周围点的 h 跟 g
h 直接取坐标距离,g 取两点距离 * 移动到该点的难度
如果周围点已经被计算过,但是本次计算的 g 要比之前的小,就更新该点的值(g 更小说明从起点经过当前点到达该点要比经过之前计算的点到达该点更近)
另外有点需要注意的是,如果刷新了 g 要把该点重新标记为未检测,这样可以用新的 g (更近的路径)来刷新该点周围的点(不然可能出现如图的后果)
完整代码
public static bool FindPath_Hexagon(STHexagonMapData mapData, MyVector2Int startPoint, MyVector2Int endPoint, out List<MyVector2Int> path)
{
MyVector2 startPos = mapData.GetPos(startPoint.x, startPoint.y), endPos = mapData.GetPos(endPoint.x, endPoint.y);
List<PathData> listAll = new List<PathData>();
listAll.Add(new PathData() { point = startPoint, fromPath = null, g = 0, h = (endPos - startPos).Dis, bHaveChecked = false });//起点
bool sussecc = false;
PathData lastPoint = null;//检查到最后的点
int count = 0;
int maxNum = mapData.nColumnCount * mapData.nRowCount + 1;//防死循环
while (!sussecc)
{
var tmpList = listAll.FindAll((pathPoint) => !pathPoint.bHaveChecked)
.OrderBy((pathPoint) => pathPoint.g + pathPoint.h)
.ThenBy((pathPoint) => pathPoint.h).ToList();
if (tmpList == null || tmpList.Count == 0 || ++count > maxNum)
{
UnityEngine.Debug.Log($"{tmpList == null} - {tmpList.Count} - {count}");
//找不到下一个点,或者到不了终点就找离终点最近的点
lastPoint = listAll.OrderBy((pathPoint) => pathPoint.h).ToList()[0];
break;
}
PathData curPath = tmpList[0];
curPath.bHaveChecked = true;//用过就改标识符
MyVector2Int[] roundPoint = mapData.GetRoundPos(curPath.point);
for (var i = 0; i < roundPoint.Length; i++)
{
//来的点不用管
if (roundPoint[i] == curPath.fromPath?.point) continue;
//跳过障碍物
if (mapData.GetNodeData(roundPoint[i]).bIsObs) continue;
int index = listAll.FindIndex((pathPoint) => pathPoint.point == roundPoint[i]);
PathData tmpPath;
float g = mapData.GetNodeData(roundPoint[i]).nDisWeight * mapData.fNodeSize + curPath.g;
//查找是不是旧点,如果是不是就新建
if (index < 0)
{
//新点
tmpPath = new PathData();
tmpPath.point = roundPoint[i];
tmpPath.fromPath = curPath;
tmpPath.g = g;
tmpPath.h = (endPos - mapData.GetPos(tmpPath.point.x, tmpPath.point.y)).Dis;
tmpPath.bHaveChecked = false;
listAll.Add(tmpPath);//添加新点
}
else
{
//旧点,如果路径更短就更新
tmpPath = listAll[index];
if (tmpPath.g > g)
{
tmpPath.g = g;
tmpPath.fromPath = curPath;
tmpPath.bHaveChecked = false;//该点路径路径刷新重置一下,让这个点再检测一次,保证该点周边的点也刷新
listAll[index] = tmpPath;//修改旧点
}
}
if (!sussecc && roundPoint[i] == endPoint)
{
//周围点是终点,结束
sussecc = true;
lastPoint = tmpPath;//指向终点的path
break;
}
}
}
path = new List<MyVector2Int>();
//链表取到来的点,一直到null,也就是起点
while (lastPoint != null)
{
path.Add(lastPoint.point);
lastPoint = lastPoint.fromPath;
}
path.Reverse();//翻转列表,从起点到终点
return sussecc;
}