改进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.调用前对比,大致对比开销,如果开销大幅度低于预加载好的路径,则走自己计算的路径