把网上的AStar算法的论述自己实现了一遍,一开始只是最基础的实现。当然,现在AStar算法已经演变出了各种优化的版本,这篇也会基于各种优化不断的更新。
如果对算法不熟悉可以看下Stanford的这篇文章,我觉得是讲解的十分仔细的了:http://theory.stanford.edu/~amitp/GameProgramming/,也附上国内的翻译:
讲讲我对上面这篇文章的理解:
(1)AStar算法的核心就在于这个公式了f(n) = g(n) + h(n),算法的效果如何也都取决于这个公式。就如文章中说的,g(n)可以看做从start到current所花费的cost,h(n)从current到end的花费。
很多人会直接将这两个当做距离来计算,这是在忽略地形等条件影响下最简单的模型。对于每一步,我们可以确定g(n)的准确值,但是h(n)很难预估正确的值(特别是在复杂且大型的场景中)。文章中也提供了几种解决方案比如waypoint等。
(2)算法维护着两张表openlist和closelist,openlist初始化时将start加入。
在循环寻找路径时,将openlist中优先级最高的元素取出,移入closelist中,表示该点已经“探测”过。对该点的周围N个neighbor进行检测,符合条件将其加入openlist中,并对openlist进行优先级排序。
既然是对基础的简单理解,就不多说直接贴上代码:
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4
5 public class AStar {
6
7 public enum POINT_TYPE
8 {
9 normal,
10 obstacle,
11 }
12
13 public class POINT:System.IComparable
14 {
15 float _gValue, _hValue;
16 public float gValue
17 {
18 get
19 {
20 return _gValue;
21 }
22 set
23 {
24 _gValue = value;
25 fValue = AStar.GetFValue(pos,gValue,hValue);
26 }
27 }
28
29 public float hValue
30 {
31 get
32 {
33 return _hValue;
34 }
35 set
36 {
37 _hValue = value;
38 fValue = AStar.GetFValue(pos, gValue, hValue);
39 }
40 }
41 public float fValue
42 {
43 get;
44 private set;
45 }
46 public Vector2 pos, parent;
47 public POINT_TYPE type;
48
49 public int CompareTo(object obj)
50 {
51 POINT pt = obj as POINT;
52 if (fValue < pt.fValue)
53 return -1;
54 else if (fValue == pt.fValue)
55 return 0;
56 else
57 return 1;
58 }
59
60
61 }
62
63 public Dictionary<Vector2, POINT> points = new Dictionary<Vector2, POINT>();
64 public static Vector2 startPt, endPt;
65
66 public List<POINT> openList = new List<POINT>();
67 public List<POINT> closeList = new List<POINT>();
68
69 public bool finish = false;
70
71
72 public static float GetFValue(Vector2 pt,float gValue,float hValue)
73 {
74 Vector2 vec1 = pt - startPt;
75 Vector2 vec2 = endPt - startPt;
76 float fac = Vector3.Cross(new Vector3(vec1.x, vec1.y, 0), new Vector3(vec2.x, vec2.y, 0)).normalized.z > 0 ? 0.01f : -0.01f;
77 return gValue + 2f * hValue + fac;
78 }
79
80 float GetManhattanDistance(Vector2 pos1, Vector2 pos2)
81 {
82 return Mathf.Abs(pos1.x - pos2.x) + Mathf.Abs(pos1.y - pos2.y);
83 }
84
85 List<POINT> GetNeighbours(Vector2 pt)
86 {
87 List<POINT> neighbouts = new List<POINT>();
88 if (points.ContainsKey(new Vector2(pt.x - 1, pt.y)))
89 neighbouts.Add(points[new Vector2(pt.x - 1, pt.y)]);
90 if (points.ContainsKey(new Vector2(pt.x + 1, pt.y)))
91 neighbouts.Add(points[new Vector2(pt.x + 1, pt.y)]);
92 if (points.ContainsKey(new Vector2(pt.x, pt.y +1)))
93 neighbouts.Add(points[new Vector2(pt.x, pt.y +1)]);
94 if (points.ContainsKey(new Vector2(pt.x, pt.y - 1)))
95 neighbouts.Add(points[new Vector2(pt.x, pt.y - 1)]);
96 return neighbouts;
97 }
98
99 public void Init(List<POINT> pts,Vector2 start,Vector2 end)
100 {
101 foreach (POINT pt in pts)
102 {
103 points.Add(pt.pos, pt);
104 }
105
106 startPt = start;
107 endPt = end;
108
109 points[startPt].parent = start;
110 points[startPt].gValue = 0;
111 points[startPt].hValue = Mathf.Abs(startPt.x - endPt.x) + Mathf.Abs(startPt.y - endPt.y);
112
113 openList.Add(points[startPt]);
114
115 finish = false;
116 }
117
118 public void StepNext()
119 {
120 if (finish)
121 return;
122
123 POINT current = openList[0];
124 openList.Remove(current);
125 closeList.Add(current);
126 if (current.pos == endPt)
127 {
128 finish = true;
129 return;
130 }
131
132 List<POINT> neighbours = GetNeighbours(current.pos);
133 for (int i = 0; i < neighbours.Count; i++)
134 {
135 if (neighbours[i].type == POINT_TYPE.obstacle || closeList.Contains(neighbours[i]))
136 continue;
137
138 bool needSort = false;
139 float gValue =GetManhattanDistance(neighbours[i].pos,startPt);
140 float hValue = GetManhattanDistance(neighbours[i].pos,endPt);
141 float fValue = AStar.GetFValue(neighbours[i].pos,gValue,hValue);
142
143 if (openList.Contains(neighbours[i]))
144 {
145 if (neighbours[i].fValue > fValue)
146 {
147 neighbours[i].gValue = gValue;
148 neighbours[i].hValue = hValue;
149 needSort = true;
150 }
151 }
152 else
153 {
154 neighbours[i].gValue = gValue;
155 neighbours[i].hValue = hValue;
156 neighbours[i].parent = current.pos;
157 openList.Add(neighbours[i]);
158 needSort = true;
159 }
160
161
162 if (needSort)
163 openList.Sort();
164 }
165
166 }
167
168 }
View Code
简单的标注这几行:
49-58 :继承于IComparable接口类,并且重写了CompareTo方法。这样就可以利用List<T>.Sort()来排序了。在CompareTo方法中,当fValue相等时,hValue值小具有更高的priority。关于fValue相同的情况在论文中有阐述。
71-77:根据g(n),h(n)计算f(n)。上面说的算法的核心公式是f(n) = g(n) + h(n),但是很多时候这样简单的相加并不能适应各种复杂的情况。考虑这样一种情况:
(请忽略这张图中坐标下的数值(f值),并不与代码相符)
蓝色为当前探测的点,黄色为待探测的点。若用公式f(n) = g(n) + h(n)此时有两种相等(f和h都相同)的情况,这样将增大计算的消耗。这里简单的用了cross函数使相等时总是能选择某一侧作为偏向。
138-139:g和h的值分别为n点到start和end点的曼哈顿距离(x,y轴上距离相加),这里其实只是近似值,并且g的值其实来说并不准确。上文说道g值对于每一步的计算来说都是可以确定的。在本例中暂且这样,下一例中改进。
来看一下算法的效果:
第一张图从(0,10)到(14,2),第二张图从(0,12)到(14,2)。绿色为算法最终输出的路径,黄色与蓝色为检测的范围。
而在实现过程中也法线了g和h对算法的影响。当g>>h时,算法偏向于全方位的检测,当h>>g时,算法偏向于向end点方向检测。