梦幻之旅2
如何寻路
这个是我最困扰的,也是我想了好久才想出来的。游戏中,你肯定要判断当前的路是不是可以走,当然在没有障碍物的情况下,你是可以随便走的,但是我的这个游戏里面有地图,有地图肯定就得有障碍物,我的想法是这样先把一张图片(320*240),按照1*1的大小拆分下来,用一个矩阵表示当前坐标的状态,例如(20,30,1)这三个参数分别表示X坐标,Y坐标,最后一个参数0表示可以到达,1表示不可到达。这样经过矩阵的初始化以后,就可以在逻辑上知道,当前玩家是否可以行走了。
下来就是寻路了,寻路是游戏开发中非常重要的一个元素,如何找到一条最短的路径是程序需要设计的算法,由于自己在算法上也有点研究,自己也想了好久,终于想出了一种办法。
在介绍我的办法之前,我先介绍两种常见的搜索算法,分别是深度优先和广度优先,广度优先是从初始状态一层一层的向下找,知道找到结果目标为止,深度优先是按照一定的顺序先查找完一个分支再查找另一个分支,知道找到目标结果为止。这两种搜索方法有的很大缺陷是它们都是在一个给定的状态空间中穷举。这在状态空间不大的情况下是很适合的算法,但是当空间很大并且不可预测的情况下就不可取。这个时候这两种算法的效率太低甚至有时是无法完成,所以当地图大时候,他们就变得很无力。
我们将以下图作为地图来进行讲解,图中对每一个方格都进行了编号,其中绿色的方格代表起点,红色的方格代表终点,蓝色的方格代表障碍,我们将用这种算法来寻找一条从起点到终点最优路径,为了方便讲解,本地图规定只能走上下左右4个方向,当你理解了我的想法后,8个方向也自然明白
图14
在地图中,每一个方格最基本也要具有两个属性值,一个是方格是通畅的还是障碍,另一个就是指向他父亲方格的指针(相当于双向链表结构中的父结点指针),我们假设方格值为0时为通畅,值为1时为障碍
在算法中,有4个相当重要的元素,第一个就是指向父亲结点的指针,第二个就是一个OPEN表,第三个就是CLOSE表,这两张表的具体作用我们在后面边用边介绍,第四个就是每个结点的F值(F值相当于图结构中的权值)
而F = H + G;其中H值为从网格上当前方格移动到终点的预估移动耗费。可能会让你有点迷惑。这样叫的原因是因为它只是个猜测。我们没办法事先知道路径的长度,因为路上可能存在各种障碍。我们定义H值为 “终点所在行”减去“当前格所在行”的绝对值 与 “终点所在列”减去“当前格所在列”的绝对值之和,而G值为从“当前格的父亲格”移动到当前格的预估移动耗费,假如当前格的父亲的移动耗费为2,那个当前格的G值就为2+1=3,在这里我们设定一个基数10,每个H和G都要乘以10,这样方便观察
好了,我们开始对地图进行搜索。
首先,我们将起点的父亲结点设置为NULL,然后将起点的G值设置为0,再装进open表里面,然后将起点作为父亲结点的周围4个点20,28,30,38(因为我们地图只能走4个方向,如果是8方向,则要加8个点进去)都加进open列表里面,并算去每个结点的H值,然后再将起点从open列表删除,放进close表中,我们将放进close表的所有方格都用浅蓝色线条进行框边处理,所以这次搜索以后,图片变为如下格式,其中箭头代表的是其父结点
图15
其中每个格子的左下方为G值,右下方为H值,左上方为F值,我们拿28号格子为例来讲解一写F值的算法,首先因为终点33在4行7列,而28在4行2列,则行数相差为0,列数相差为5,总和为5,再乘以我们先前定的基数10,所以H值为50,又因为从28的父结点29移动到28,长度为1格,而29号为起点,G值为0,所以在父亲结点29的基础上移动到28所消耗的G值为(0 + 1) *10 = 10,0为父亲结点的G值,1为从29到28的消耗。
当前OPEN表中的值: 20,28,30,38 当前CLOSE表中的值: 29。
现在我们开始寻找OPEN列表中F值最低的,得出结点30的F值最低,且为40,然后将结点30从OPEN表中删除,然后再加入到CLOSE表中,然后在判断结点30周围4个结点,因为结点31为障碍,结点29存在于CLOSE表中,我们将不处理这两点,只将21和39号结点加入OPEN表中,添加完后地图变为下图样式
当前OPEN表中的值: 20,28,38,21,39 当前CLOSE表中的值: 29,30
图16
接着我们重复上面的过程,寻找OPEN表中F值为低的值,我们发现OPEN表中所有结点的F值都为60,我们随即取一个结点,这里我们直接取最后添加进OPEN表中的结点,这样方便访问(因为存在这样的情况,所有从一个点到另外一个点的最短路径可能不只一条),我们取结点39,将他从OPEN表中删除,并添加进CLOSE表中,然后观察39号结点周围的4个结点,因为40号结点为障碍,所以我们不管它,而30号结点已经存在与OPEN表中了,所以我们要比较下假设39号结点为30号结点的父结点,30号结点的G值会不会更小,如果更小的话我们将30结点的父结点改为39号,这里我们以39号结点为父结点,得出30号结点的新G值为20,而30号结点原来的G值为10,并不比原来的小,所以我们不对30号进行任何操作,同样的对38号结点进行上述操作后我们也不对它进行任何操作,接着我们把48号结点添加进OPEN表中,添加完后地图变为下图样式。
当前OPEN表中的值: 20,28,38,21,48 当前CLOSE表中的值: 29,30,39。
图17
以后的过程中我们都重复这样的过程,一直到遍历到了最后终点,通过遍历父结点编号,我们能够得出一条最短路径,具体完整的推导过程我就不写出来了,因为和刚才那几步是一样的,这里我再讲出一个特例,然后基本上算法就没问题了
上面的最后一推导中,我们在观察39号结点时,发现他周围已经有结点在OPEN表中了,我说"比较下假设39号结点为30号结点的父结点,30号结点的G值会不会更小,如果更小的话我们将30结点的父结点改为39号",但是刚才没有遇到G值更小的情况,所以这里我假设出一种G值更小的情况,然后让大家知道该怎么操作,假设以39号为父结点,我们得出的30号的新G值为5(只是假设),比30号的原G值10还要小,所以我们要修改路径,改变30号的箭头,本来他是指向29号结点的,我们现在让他指向39号结点,38号结点的操作也一样
好了,算法的大体思路就是这样了,对于8方向的地图来说,唯一的改变就是G值方面,在上下左右,我们的G值是加10,但是在斜方向我们要加14(为什么是14,你可以想想勾股定理)。
下面上代码
1 public class Stack 2 { 3 private Node node; 4 private Stack next; 5 public Node Node 6 { 7 get { return node; } 8 set { node = value; } 9 } 10 public Stack Next 11 { 12 get { return next; } 13 set { next = value; } 14 } 15 public Stack() 16 { } 17 }
1 public class Node 2 { 3 private int f; 4 private int h; 5 private int g; 6 private int x; 7 private int y; 8 private int index;//对应节点编号索引 9 private Node parent; 10 private Node[] child; 11 private Node next; 12 13 public int F 14 { 15 get { return f; } 16 set { f = value; } 17 } 18 public int H 19 { 20 get { return h; } 21 set { h = value; } 22 } 23 public int G 24 { 25 get { return g; } 26 set { g = value; } 27 } 28 public int X 29 { 30 get { return x; } 31 set { x = value; } 32 } 33 public int Y 34 { 35 get { return y; } 36 set { y = value; } 37 } 38 public int Index 39 { 40 get { return index; } 41 set { index = value; } 42 } 43 public Node Parent 44 { 45 get { return parent; } 46 set { parent = value; } 47 } 48 public Node[] Child 49 { 50 get { return child; } 51 set { child = value; } 52 } 53 public Node Next 54 { 55 get { return next; } 56 set { next = value; } 57 } 58 59 60 public Node(int x, int y) 61 { 62 this.x = x; 63 this.y = y; 64 this.child = new Node[8]; 65 } 66 public Node() 67 { 68 this.child = new Node[8]; 69 } 70 }
1 public class MapTerrain 2 { 3 private int _mapPositionX; 4 public int MapPositionX 5 { 6 get 7 { 8 return (this._mapPositionX); 9 } 10 set 11 { 12 this._mapPositionX = value; 13 } 14 } 15 16 private int _mapPositionY; 17 public int MapPositionY 18 { 19 get 20 { 21 return (this._mapPositionY); 22 } 23 set 24 { 25 this._mapPositionY = value; 26 } 27 } 28 29 private int _terrain; 30 public int Terrain 31 { 32 get 33 { 34 return (this._terrain); 35 } 36 set 37 { 38 this._terrain = value; 39 } 40 } 41 42 public MapTerrain() 43 { } 44 }
1 public class AStart 2 { 3 private Node open; 4 private Node close; 5 private Stack stack; 6 public int[] indexs = new int[30 * 30]; 7 public Point ScreenCenter; 8 public AStart() 9 { 10 this.Inivate(); 11 } 12 13 public void SetScreenCenter(Point Point) 14 { 15 ScreenCenter = Point; 16 } 17 18 19 public void SetBlock(List<MapTerrain> MapTerrainList, Point Point) 20 { 21 ScreenCenter = Point; 22 List<MapTerrain> NowMapTerrainList = new List<MapTerrain>(); 23 foreach (MapTerrain MapTerrain in MapTerrainList) 24 { 25 if (MapTerrain.MapPositionX >= Point.X / 320 * 10 - 10 && MapTerrain.MapPositionX < Point.X / 320 * 10 + 20 26 && MapTerrain.MapPositionY >= Point.Y / 240 * 10 - 10 && MapTerrain.MapPositionY < Point.Y / 240 * 10 + 20) 27 { 28 indexs[(MapTerrain.MapPositionY - Point.Y / 240 * 10 + 10) * 30 + MapTerrain.MapPositionX - Point.X / 320 * 10 + 10] = MapTerrain.Terrain; 29 } 30 } 31 } 32 33 /// <summary> 34 /// 初始化,默认为0,表示边界不可行走 35 /// </summary> 36 private void Inivate() 37 { 38 int c, index = 0;//设置边界 39 for (c = 0; c < 30; c++) 40 { 41 indexs[c] = 0;//初始化 正方形的上边 30个 42 } 43 for (c = 1; c < 29; c++) 44 { 45 index += 30;//换行 加等30 即换行 46 indexs[index] = 0;//初始化 正方形的左边 28个 47 indexs[index + 29] = 0; //初始化 正方形的右边 28个 48 } 49 index += 29; 50 for (c = index; c < 30 * 30; c++) 51 { 52 indexs[c] = 0; //初始化 正方形的下边 30个 53 } 54 55 open = new Node(); 56 close = new Node(); 57 stack = new Stack(); 58 } 59 60 public List<Node> FindPath(Point StarPoint, Point EndPoint) 61 { 62 Inivate();//初始化,设置边界 63 StarPoint = new Point(StarPoint.X - StarPoint.X % 32, StarPoint.Y - StarPoint.Y % 24); 64 EndPoint = new Point(EndPoint.X - EndPoint.X % 32, EndPoint.Y - EndPoint.Y % 24); 65 List<Node> path = new List<Node>(); 66 if (indexs[GetIndex(EndPoint.X, EndPoint.Y)] == 0) 67 return path; 68 Node node, bestnode = null; 69 int bestIndex = this.GetIndex(EndPoint.X, EndPoint.Y); 70 node = new Node(StarPoint.X, StarPoint.Y); 71 node.G = 0; 72 node.H = (StarPoint.X - EndPoint.X) * (StarPoint.X - EndPoint.X) + (StarPoint.Y - EndPoint.Y) * (StarPoint.Y - EndPoint.Y);//如果开平方就不精确了 73 node.F = node.H + node.G; 74 node.Index = this.GetIndex(StarPoint.X, StarPoint.Y); 75 open.Next = node; 76 while (true) 77 { 78 bestnode = this.GetBestNode(); 79 if (bestnode != null) 80 { 81 if (bestnode.Index.Equals(bestIndex)) 82 break; 83 this.GenerateSuccessors(bestnode, EndPoint); 84 } 85 else 86 break; 87 } 88 this.ShowPath(path, bestnode); 89 return path; 90 } 91 92 private void ShowPath(List<Node> list, Node bestnode) 93 { 94 if (bestnode != null) 95 { 96 if (bestnode.Parent != null) 97 { 98 this.ShowPath(list, bestnode.Parent); 99 } 100 list.Add(bestnode); 101 } 102 } 103 104 private void GenerateSuccessors(Node bestnode, Point EndPoint) 105 { 106 int x, y = 0; 107 for (int i = 0; i < 8; i++) 108 { 109 switch (i) 110 { 111 case 0://东 112 x = bestnode.X + 32; 113 y = bestnode.Y; 114 if (HasIndex(x, y)) 115 this.GenerateSucc(bestnode, x, y, EndPoint, 10); 116 break; 117 case 1://南 118 x = bestnode.X; 119 y = bestnode.Y + 24; 120 if (HasIndex(x, y)) 121 this.GenerateSucc(bestnode, x, y, EndPoint, 10); 122 break; 123 case 2://西 124 x = bestnode.X - 32; 125 y = bestnode.Y; 126 if (HasIndex(x, y)) 127 this.GenerateSucc(bestnode, x, y, EndPoint, 10); 128 break; 129 case 3://北 130 x = bestnode.X; 131 y = bestnode.Y - 24; 132 if (HasIndex(x, y)) 133 this.GenerateSucc(bestnode, x, y, EndPoint, 10); 134 break; 135 case 4://东北 136 x = bestnode.X + 32; 137 y = bestnode.Y - 24; 138 if (HasIndex(x, y)) 139 this.GenerateSucc(bestnode, x, y, EndPoint, 14); 140 break; 141 case 5://东南 142 x = bestnode.X + 32; 143 y = bestnode.Y + 24; 144 if (HasIndex(x, y)) 145 this.GenerateSucc(bestnode, x, y, EndPoint, 14); 146 break; 147 case 6://西南 148 x = bestnode.X - 32; 149 y = bestnode.Y + 24; 150 if (HasIndex(x, y)) 151 this.GenerateSucc(bestnode, x, y, EndPoint, 14); 152 break; 153 case 7://西北 154 x = bestnode.X - 32; 155 y = bestnode.Y - 24; 156 if (HasIndex(x, y)) 157 this.GenerateSucc(bestnode, x, y, EndPoint, 14); 158 break; 159 } 160 } 161 } 162 163 private void GenerateSucc(Node bestnode, int x, int y, Point EndPoint, int step) 164 { 165 int g, index, c = 0; 166 g = bestnode.G + step; 167 index = this.GetIndex(x, y); 168 Node old, successor = null; 169 if ((old = CheckOpen(index)) != null) 170 { 171 for (c = 0; c < 8; c++) 172 if (bestnode.Child[c] == null) 173 break; 174 bestnode.Child[c] = old; 175 if (g < old.G) 176 { 177 old.Parent = bestnode; ; 178 old.G = g; 179 old.F = g + old.H; 180 } 181 } 182 else if ((old = CheckClose(index)) != null) 183 { 184 for (c = 0; c < 8; c++) 185 if (bestnode.Child[c] == null) 186 break; 187 bestnode.Child[c] = old; 188 if (g < old.G) 189 { 190 old.Parent = bestnode; 191 old.G = g; 192 old.F = g + old.H; 193 this.PropagateDown(old, step); 194 } 195 } 196 else 197 { 198 successor = new Node(x, y); 199 successor.Parent = bestnode; 200 successor.G = g; 201 successor.H = (x - EndPoint.X) * (x - EndPoint.X) + (y - EndPoint.Y) * (y - EndPoint.Y); 202 successor.F = g + successor.H; 203 successor.Index = index; 204 this.Insert(successor); 205 for (c = 0; c < 8; c++) 206 if (bestnode.Child[c] == null) 207 break; 208 bestnode.Child[c] = successor; 209 } 210 } 211 212 private void PropagateDown(Node old, int step) 213 { 214 int c, g = 0; 215 Node child, father = null; 216 g = old.G; 217 for (c = 0; c < 8; c++) 218 { 219 if ((child = old.Child[c]) == null) 220 break; 221 if (g + step < child.G) 222 { 223 child.G = g + step; 224 child.F = child.G + child.H; 225 child.Parent = old; 226 this.Push(child); 227 } 228 } 229 while (stack.Next != null) 230 { 231 father = this.Pop(); 232 for (c = 0; c < 8; c++) 233 { 234 if ((child = father.Child[c]) == null) 235 break; 236 if (father.G + step < child.G) 237 { 238 child.G = father.G + step; 239 child.F = child.G + child.H; 240 child.Parent = father; 241 this.Push(child); 242 } 243 } 244 } 245 } 246 247 private void Insert(Node successor) 248 { 249 Node tmp1, tmp2 = null; 250 int f = 0; 251 if (open.Next == null) 252 { 253 open.Next = successor; 254 return; 255 } 256 f = successor.F; 257 tmp1 = open; 258 tmp2 = open.Next; 259 while ((tmp2 != null) && (tmp2.F < f)) 260 { 261 tmp1 = tmp2; 262 tmp2 = tmp2.Next; 263 } 264 successor.Next = tmp2; 265 tmp1.Next = successor; 266 } 267 268 private Node CheckOpen(int index) 269 { 270 Node temp = null; 271 temp = open.Next; 272 while (temp != null) 273 { 274 if (temp.Index.Equals(index)) 275 return temp; 276 else 277 temp = temp.Next; 278 } 279 return temp; 280 } 281 282 private Node CheckClose(int index) 283 { 284 Node temp = null; 285 temp = close.Next; 286 while (temp != null) 287 { 288 if (temp.Index.Equals(index)) 289 return temp; 290 else 291 temp = temp.Next; 292 } 293 return temp; 294 } 295 296 private bool HasIndex(int x, int y) 297 { 298 try 299 { 300 if (!indexs[GetIndex(x, y)].Equals(0))//该节点不是障碍物 301 return true; 302 else 303 return false; 304 } 305 catch (Exception) 306 { 307 308 return false; 309 } 310 } 311 /// <summary> 312 /// 寻找最优点 313 /// </summary> 314 /// <returns></returns> 315 private Node GetBestNode() 316 { 317 Node temp = null; 318 if (open.Next != null) 319 { 320 temp = open.Next; 321 open.Next = temp.Next; 322 temp.Next = close.Next; 323 close.Next = temp; 324 } 325 return temp; 326 } 327 328 public int GetIndex(int x, int y) 329 { 330 return (y - ScreenCenter.Y + ScreenCenter.Y % 240 + 240) / 24 * 30 + (x - ScreenCenter.X + ScreenCenter.X % 320 + 320) / 32; 331 } 332 333 334 private void Push(Node node) 335 { 336 Stack temp = new Stack(); 337 temp.Node = node; 338 temp.Next = stack.Next; 339 stack.Next = temp; 340 } 341 342 private Node Pop() 343 { 344 Node tempNode = null; 345 Stack tempStack = null; 346 tempStack = stack.Next; 347 tempNode = tempStack.Node; 348 stack.Next = tempStack.Next; 349 return tempNode; 350 } 351 }