改进OpenList开启列表
使用优先队列代替List
使用有限队列能够节约对OpenList的遍历
优先队列
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace YBZ.Algorithm {
public class PriorityQueue<T> where T : new ()
{
public int size;
public int capacity;
private T[] elements;
// 是否为空
public bool IsEmpty { get => size == 0; }
/// 范围顶部元素
public T Top { get => elements[0]; }
/// 优先队列的模式
private PriorityQueueMode _comparator;
public enum PriorityQueueMode {
less = -1, // 最小优先队列
equal = 0, // 相等的排在一起
greater = 1 // 最大优先队列
}
/// <summary>
/// 以CMP(a,b) 为例:
/// 当a>b时,返回1,表示放右边
/// 当a==b时,返回0,表示不变
/// 当a<b时,返回-1,表示放左边
/// </summary>
private Func<T,T,int> CMP;
/// <summary>
/// 构造函数, 必须实现
/// </summary>
/// <param name="CMP"></param>
/// <param name="capacity"></param>
/// <param name="priorityQueueMode"></param>
public PriorityQueue(Func<T,T,int> CMP, PriorityQueueMode priorityQueueMode = PriorityQueueMode.less, int capacity = 1) {
this.CMP = CMP;
this.size = 0; // 数组索引从0开始
this.capacity = capacity;
this.elements = new T[capacity];
this._comparator = priorityQueueMode;
}
/// <summary>
/// 入队
/// </summary>
/// <param name="value"></param>
public void Push(T value) {
if (size == capacity) {
ExpandCapacity();
}
elements[size++] = value;
ShiftUp();
}
/// <summary>
/// 出队
/// </summary>
public void Pop() {
if(size == 0) {
return;
}
size--;
Swap(ref elements[0], ref elements[size]);
ShiftDown();
}
/// <summary>
/// 清空队列
/// </summary>
public void Clear() {
size = 0;
}
/// <summary>
/// 返回位于Queue开始处的对象但不将其移除。
/// </summary>
/// <returns>返回第一个队列中元素</returns>
public T Peek() {
return Top;
}
/// <summary>
/// 扩展队列的容量
/// </summary>
private void ExpandCapacity() {
capacity = Mathf.CeilToInt(capacity * 1.5f);
T[] temp = new T[capacity];
for (int i = 0; i < elements.Length; i++) {
temp[i] = elements[i];
}
elements = temp;
}
// 从下到上 重排序
private void ShiftUp() {
int cur = size - 1 ;
int parent = ( cur -1 ) >> 2;
while (cur > 0)
{
if (CMP(elements[cur],elements[parent]) == (int)_comparator) {
Swap(ref elements[cur], ref elements[parent]);
cur = parent;
parent = (cur - 1) >> 2;
} else break;
}
}
// 从上到下 重排序
private void ShiftDown() {
int cur = 0;
int child = 1;
while (child < size) {
if (child + 1 < size && CMP(elements[child +1], elements[child]) == (int)_comparator) {
child++;
}
if (CMP(elements[child], elements[cur]) == (int)_comparator){
Swap(ref elements[child], ref elements[cur]);
cur = child;
child = cur << 1 + 1;
} else break;
}
}
/// <summary>
/// 交换传入的两个元素
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
private void Swap(ref T lhs,ref T rhs) {
T temp = lhs;
lhs = rhs;
rhs = temp;
}
/// <summary>
/// 返回队列中的所有元素,对于ToString()函数,值类型会返回值,引用类型会返回数据类型
/// </summary>
/// <returns></returns>
public override string ToString() {
string result = "";
foreach (var v in elements) {
result += v.ToString();
}
return result;
}
}
}
改进F = G + H启发式
加权函数
采用加权函数W(n)将启发函数改进为F = G + W * H
对于W为H的系数函数,当H越大的时候W返回的越大,要求尽快的达到目标区域;当H越小,则要求路径越准确
// 加权函数 ,具体比例可以自己决定,
public int W(int H) {
int w = 1;
if (w > 500) {
w = 5;
} else if (w > 300) {
w = 4;
} else if(w > 100){
w = 3;
}else if (w > 50) {
w = 2;
} else {
w = 1;
}
return w;
}
减少拐点
同时也可以在Cost上增加系数要求尽可能走直线,使走斜线是走直线代价的两倍,但是这样只能由于特定要求,比如需要走直线的时候,否侧我认为无论是走直线,走斜线效果一样(走直线会减少邻居节点的添加)
/// <summary>
/// 额外代价
/// </summary>
/// <param name="current">当前节点</param>
/// <param name="neighbor">邻居节点</param>
/// <param name="goal">终点</param>
/// <returns></returns>
public double Cost(Node current,Node neighbor, Node goal)
{
Node parent = current.parent;
// 起点
if (parent == null) return 0;
// 走直线
if (neighbor.x == parent.x || neighbor.y == parent.y) return 0;
// 拐向终点的点
if (neighbor.x == goal.x || neighbor.y == goal.y) return 1;
// 普通拐点
return 2;
}
可穿障碍物
对Cost函数上如果是障碍物就大幅度增加开销,而不是直接忽略
1.正方形节点,如果正常开销是10,那么障碍物上的开销就必须大幅度大于10, cost == 100,
2.一般还是不存在可穿障碍物
预加载邻居节点
优化Node的GetNeighbor函数使其能够在路径搜索开启前就已经完成。
using System.Linq;
public override List<NodeBase> CacheNeighbors()
{
return Neighbor = GridManager.Instance.Tiles.Where(t => Coords.GetDistance(t.Value.Coords) == 1).Select(t => t.Value).ToList();
}
JPS(Jump Point Search)
跳点搜索,优先走斜线,尽可能才减掉不需要的邻居节点
相对于矩形节点,走直线只会得到三个新的邻居节点
走斜线,就能得到五个新的邻居节点,能够快速增加自己节点搜索
Map优化
控制维度
控制Map的大小
分层优化(预加载路径)
1.将大面积的的地区先作为一个节点,比如从莫斯科到北京就要求先从俄罗斯到中国,在这过程中逐步缩小范围,莫斯科到中国边境,再从边境到北京市,在到目的地。
2.对于俄罗斯到中国的路径,采用预加载模式,每次从俄罗斯到中国都采用这条路径。
3.调用前对比,大致对比开销,如果开销大幅度低于预加载好的路径,则走自己计算的路径