技术开发 频道

开发Silverlight游戏教程:A*寻径算法

    【IT168技术文档】关于地图引擎方面的处理涉及到两个方面的知识:

1)地图的实现(包括地图的切割、合成、呈现方式等)

2)地图物件的实现(包括地图中实现寻路、遮罩、传送点等)

    为了让大家能更加有兴趣深入后面的知识,我选择先从地图寻路开始讲解吧:

    目前游戏中的寻路最经典的莫过于A*(A Star)寻路了,它是一种寻路思维的合集,那么基于它产生的算法则又有多种,例如曼哈顿启发式算法、对角线取径算法、欧几里德几何算法、最大取值算法等等,不同的算法产生的效果不同:如计算出路径需要的总时间,得到的路径长短优劣等,并且参数的不同也将导致结果的大异。我借助国外一位牛人的A*寻径工具(有兴趣的朋友可以在http://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/article.php/c12527/中找到相关资源),分别通过对各种路径使用各种A*算法来寻径,最终通过花费时间和路径的优美性进行了横向与纵向的比较评分,得出速度最快的算法:曼哈顿启发式算法。它在所有的包括就算九曲十八弯的复杂路径计算中均能表现极其优异的速率,下图为测试的部分截图:
 

     图中棕色格子代表障碍物,起点位于左上角,终点在中间的红色边框格子,蓝色格子代表找到的路径。从图中右下角可以看到,就算如此复杂的充满分支的迷宫中仍可以在3毫秒中找到非常好的路径,这是极其优异的。那么很多朋友看到这可能会感觉如此复杂的程序,哪是一般人能写出来的?其实说难还是有一些难度的,但是幸运的是,我们的同胞已经将一篇入门文章翻译出来了,我看了一下很不错,该翻译文章地址如下(看该文章请有耐心不长不短,但是看完以后将有非常大的收获!):http://data.gameres.com/message.asp?TopicID=25439,那么我们该如何通过C#代码来实现曼哈顿A*呢?我抛砖引玉简单原理描述一下:从起点开始发散式的寻找周围8个点,通过各种条件筛选出最优的那个点,然后将此点再作为起点继续循环以上过程,最后我们得到的所有起点集合即为最终路径。是不是觉得不太难了?呵呵,那么大家动手写写吧!(大家完全可以参考我给大家的那位外国牛人写的程序,里面有源码,通过参考源码,相信大家花些时间完全可以轻易的实现自己的A*)

    接着,我将自己写好的曼哈顿A*寻径所有代码封装在QX.Game.PathFinder这个命名空间中,那么到此才进入本文的关键,如何通过C#来模拟角色寻路:

    首先当然是引用:using QX.Game.PathFinder;

    接着我们初始化需要的变量:

 Rectangle rect;

private IPathFinder PathFinder = null;

private byte[,] Matrix = new byte[1024, 1024]; //寻路用二维矩阵

private int GridSize = 20; //单位格子大小

private System.Drawing.Point Start = System.Drawing.Point.Empty; //移动起点坐标

private System.Drawing.Point End = System.Drawing.Point.Empty; //移动终点坐标

    这里要特别讲解一下GridSize这个变量,它定义了窗口坐标系中以多大一个尺寸来确定游戏坐标系的一个单元格(大家可以这样理解这两种不同的坐标系:假如游戏窗口大小为800*600像素,那么窗口坐标系中的(80,100)这个坐标,根据GridSize = 20来换算,在游戏坐标系中的坐标则为(80/20,100/20)=(4,5))。大家同时可以联想一下SLG类型游戏,人物处于的每个单元格都是由N*N像素组成的方块,GridSize就相当于N了;而该格子在游戏坐标系中的显示坐标则为((N/20), (N/20)),这样应该很好理解了吧。这样根据不同的需要来使用GridSize对坐标系进行缩小(/GridSize)和放大(*GridSize)操作,从而可以非常方便的实现各种效果并且被不同的情况所调用,后面的内容及章节会涉及到相关知识。

    那么接下来我们在窗体构造函数中初始化二维矩阵,代码如下:

 

public Window7() {

InitializeComponent();

ResetMatrix(); //初始化二维矩阵

}

private void ResetMatrix() {

for (int y = 0; y < Matrix.GetUpperBound(1); y++) {

for (int x = 0; x < Matrix.GetUpperBound(0); x++) {

//默认值可以通过(非障碍物)在矩阵中用1表示

Matrix[x, y] = 1;

}

}

//构建障碍物(举例)

for (int i = 0; i < 18; i++) {

//障碍物在矩阵中用0表示

Matrix[i, 12] = 0;

rect = new Rectangle();

rect.Fill = new SolidColorBrush(Colors.Red);

rect.Width = GridSize;

rect.Height = GridSize;

Carrier.Children.Add(rect);

Canvas.SetLeft(rect, i * GridSize);

Canvas.SetTop(rect, 12 * GridSize);

}

//构建其他障碍物……(省略)

Start = new System.Drawing.Point(1, 1); //设置起点坐标

}

    那么有了我们前面6节的知识基础并结合相应的注释,这些代码应该很容易可以接受。主要作用是定义起点,初始化矩阵中所有元素(默认都是可以通行的赋值1),然后我们可以设置些障碍物来测试我们寻径的效果,即根据需要将矩阵中需要变成障碍物的元素赋值0,这样我们就将所有的准备工作做好了。

    最后就是如何实现寻径啦,代码如下:

 

private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

Point p = e.GetPosition(Carrier);

int x = (int)p.X / GridSize;

int y = (int)p.Y / GridSize;

End = new System.Drawing.Point(x, y); //计算终点坐标

 

PathFinder = new PathFinderFast(Matrix);

PathFinder.Formula = HeuristicFormula.Manhattan; //使用我个人觉得最快的曼哈顿A*算法

PathFinder.SearchLimit = 2000; //即移动经过方块(20*20)不大于2000个(简单理解就是步数)

 

List<PathFinderNode> path = PathFinder.FindPath(Start, End); //开始寻径

 

if (path == null) {

MessageBox.Show("路径不存在!");

} else {

string output = string.Empty;

for (int i = path.Count - 1; i >= 0; i--) {

output = string.Format(output

+ "{0}"

+ path[i].X.ToString()

+ "{1}"

+ path[i].Y.ToString()

+ "{2}",

"(", ",", ") ");

rect = new Rectangle();

rect.Fill = new SolidColorBrush(Colors.Green);

rect.Width = GridSize;

rect.Height = GridSize;

Carrier.Children.Add(rect);

Canvas.SetLeft(rect, path[i].X * GridSize);

Canvas.SetTop(rect, path[i].Y * GridSize);

}

MessageBox.Show("路径坐标分别为:" + output);

}

}

    这里我将鼠标左键点击的点作为寻径的动态终点,然后创建寻径类PathFinder,接着定义好参数后就可以通过List<PathFinderNode> path = PathFinder.FindPath(Start, End);来实现寻径了。最后通过MessageBox将我们找到的路径点逐一打印出来,至此就完成了我们完美的曼哈顿A*寻径了。

 


    上图为程序运行图,绿色代表找到的路径,红色代表障碍物,找到的路径同样如此的完美!是不是很有成就感?

    有了这A*算法寻径类,可以说地图引擎就好比完成了一半不为过;那么下一节我将介绍如何通过此节获取的List<PathFinderNode> path列表来实现按照此列表实现的动态关键帧动画,敬请期待。

0
相关文章