前面几节详细的讲解了游戏地图的完整构造,比较有难度的是关于地图内层如障碍物的实现。A*算法往往能让众多的初学者望而止步,斜度α地图则更需要一定的几何知识及抽象思维。很多朋友就问了:什么年代了,都在说面向对象、提高开发效率,难道就没有大众化可以让各层次能力的朋友们都能轻松制作地图引擎的方法吗?大家是否还记得上一节中遗留的一个小悬念,杀手涧就是它了:神奇的副本地图。

      大家先看上图,左边的是地图表现层,它的尺寸为800*600。右边的则是我通过Photoshop在原图基础上勾勒出来的该地图的副本,同样它的尺寸也为800*600。这里特别要提的是该副本是由简单纯色调组成的,因此能够压缩到极小的容量,几乎忽略不计,这是它能作为我们得力工具的前提,也是Silverlight制作基于网页游戏的必要条件。好了,接下来我们详细介绍一下此副本:大家对照原图很容易会发现它上面的黑色其实代表的就是地图中的障碍物,那大片的白色区域呢?其实就是我们可以任意通行的区域了。至于黄色,聪明的朋友应该也不难猜到,它代表的是地图中的传送点。当然,您还可以在此副本中增加例如红色代表陷阱,绿色代表特殊NPC等等。是否觉得像画画一样的?嘿嘿,这就是我主张的面向对象的游戏编程创新思想了。到此地图副本制作完成了,那么该如何利用它呢?

    精华又出现啦,来看看优美的拾色方法(此时,我们需要将Deeper.jpg副本图片按照第五节中的方法添加进Map文件夹中,以便被下面方法更好的识别)

        //图片拾色

        private Color pickColor(BitmapSource bitmapsource, int x, int y) {

            CroppedBitmap crop = new CroppedBitmap(bitmapsource as BitmapSource, new Int32Rect(x, y, 1, 1));

            byte[] pixels = new byte[4];

            try {

                crop.CopyPixels(pixels, 4, 0);

                crop = null;

            } catch (Exception ee) {

                MessageBox.Show(ee.ToString());

            }

            //pixels[0] 绿pixels[1]  pixels[2] 透明度pixels[3]

            return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);

        }

      太强大了,有了它就好比吕布拿上方天画戟-游刃有余!(该方法只能在WPF使用,至于如何在Silverlight中调用,Silverlight3.0将会给您一个完美的解决方案。^_^

副本地图的作用是非常凶猛的,在它上面我们可以自由绘画出红黄蓝绿青橙紫等等N多颜色来描绘不同的地图属性,然后实现类似以下操作:

  1、如果主角采到的点是黑色就相当于主角碰到了障碍物,这时主角的动作即为停止。

  2、如果是传送点,则根据坐标范围(或其他条件等)判断是传送到哪张地图;

  3、如果是陷阱则将触发什么事件,如去血或被传送,或是刷怪等等;

  4、当然还可以有其他颜色,假如游戏中有飞行坐骑等元素存在(实现2D地图中的三维空间),那么同样可以用一个例如蓝色来代表空中障碍物区域,或用紫色来代表陆地和空中均属的障碍物,这些都是相当灵活的。

  5、白的则为可以通行,主角在上面可以正常移动。

    更美妙的是,此方法可以与A*寻径相结合,从而创造出更加优美的角色移动(例如帝国时代中采取的就是它独特的改进型A*,所以根据游戏自身的特点您可以对A*进行优化,Gameres论坛有很多高手的文章,大家可以参考一下)。接下来大家来回忆一下第七讲到的A*寻径算法,此方法找到的路径中如果有经过障碍物倒还好,但是如果没有障碍物的,那么此路径全程中将或多或少会有些折叠的地方(如下图)。

      人类总是希望将东西做得完美导致本节的重点出现了:如何通过神奇的副本地图来优化A*寻路,让它更加贴近真实呢?这里我们需要先理解一个关键知识点:主角所处的地图中所有的点与副本地图中所有的点都是一一对应(映射)的关系。例如假设主角在地图中的坐标为(356,248),那么此坐标对应副本地图坐标也同样为(356,248),这样我们就可以通过函数方法,将主角的坐标点作为参数在副本地图中找该点的颜色,看看颜色分别是黑的,还是白的,或是黄的等等,从而映射回主角的地图可知主角当前处于地图中是障碍物,还是可同行区域,或是传送点等等。了解了原理后,下面我就用代码来实现它:

      首先我们需要写出两种移动方法(具体代码就不列出来了,在本教程的目录中有下载):第一种我定义为NormalMove移动方法,它就是我第一节中讲到的点与点之间的直线移动,在此方法中我稍微改动了一些,使坐标定位到主角的脚底,并且将移动目标终点记录到Point Target中。第二我将之定义为AStarMove移动方法,它就是我前面几节讲到的A*寻路方式。设置好后,我们就可以根据从副本地图中获取的点的颜色判断来调用相应的移动模式了。

      那么在主角移动的时候,它的XY坐标属性是时时更新的,因此我们需要一个线程去捕获它,并且在此线程中时时判断主角是否采在了黑色点上(障碍物)。那么这里我采用了第二节中所讲到的CompositionTarget界面线程,注册了该线程dispatcherTimer1_Tick事件后,接下来就进入关键代码了:

        private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

            Point p = e.GetPosition(Carrier);

//假如点到的地方不是障碍物

            if (pickColor(Deeper, (int)p.X, (int)p.Y) != Colors.Black) {

                target = p;

                NormalMove(p); //直线移动

                //AStarMove(p); //A*寻路移动

            }

        }

        BitmapSource Deeper = new BitmapImage((new Uri(@"Map\Deeper.jpg", UriKind.Relative))); //设置地图副本

        int X, Y; //主角当前的窗口真实坐标(非缩放)

        Point target; //主角移动的最终目的

        private void dispatcherTimer1_Tick(object sender, EventArgs e) {

            X = Convert.ToInt32(Canvas.GetLeft(Spirit) + SpiritCenterX * GridSize);

            Y = Convert.ToInt32(Canvas.GetTop(Spirit) + SpiritCenterY * GridSize);

            //message.Text = "坐标:" + X + "  " + Y;

            message.Text = pickColor(Deeper, X, Y).ToString();

            //假如碰到障碍物则采用A*寻路

            if (pickColor(Deeper, X, Y) == Colors.Black) {

                AStarMove(target);

            } else if (pickColor(Deeper, X, Y) == Colors.Yellow) {

                //假如是传送点则条到坐标(200,20)

                storyboard.Stop();

                Canvas.SetLeft(Spirit, 200 - SpiritCenterX * GridSize);

                Canvas.SetTop(Spirit, 20 - SpiritCenterY * GridSize);

            }

            //用白色点记录移动轨迹

            rect = new Rectangle();

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

            rect.Width = 5;

            rect.Height = 5;

            Carrier.Children.Add(rect);

            Canvas.SetLeft(rect, X);

            Canvas.SetTop(rect, Y);

        }

      上面代码已经进行了很详细的描述:首先我在鼠标左键事件中判断点击的地方是否是障碍物(点击的点在副本地图中是否是黑色的),是的话主角就不移动,否则就启动简单的直线移动。接下来我们需要设置几个变量它们分别存储副本地图的图片源、主角的XY精确坐标以及主角最终移动目的点。设置好后最后就是在界面线程中时时获取主角的XY坐标,并且判断主角当前位置是否是障碍物了。如果是则将当前的移动由普通直线移动转换成A*寻路移动;同时我们还可以判断如果是黄色的话则传送到点(200,20),当然大家还可以设置其他很多颜色来启动相应的事件,是不是很神奇?嘿嘿。

      下面两图分别是采用A*寻路的主角移动和改进型的A*寻路(直线移动+A*寻路)的主角移动:

      从上图可以明显看到,单纯的使用A*进行主角移动及饶过障碍物是不自然的,路线很机械且并不真实。而通过直线移动+副本地图+A*实现的改进型A*寻路所实现的主角移动则可谓几乎接近完美,与现实吻合。

      这里要顺带提一下的是:本节只为演示需要所以我使用的副本地图只有一窗口大小,因此当主角移动到窗口外时pickColor()方法的参数XY超出了副本地图的大小导致抛出异常并使程序关闭这是肯定的而并非BUG,这样也同样证明了pickColor()方法的正确性。

    小结:关于如何对A*进行改进其实也并非一定需要配合该副本地图,我们同样的可以通过时时比较主角的(SpiritWindowXSpiritWindowY)坐标对应的障碍物Matrix[SpiritWindowX, SpiritWindowY]是否==1来判断是否启动寻路。但是本节的目的是要告诉大家副本地图的万用功能,别的方法能做的,它同样能做,而且它扩展性更强,描述对象更简单且直观,这才叫解放思想、面向对象!其实类似此副本地图的使用在魔兽世界等著名的游戏中都有用到,只是我这个算是简单版的。虽然它构造简单但是在辅助地图引擎方面却显示出强大的威力。目前我感到唯一的缺憾就是还没有研究出如何使用它单独的进行有效精确的寻径(即完全抛弃复杂的A*寻径算法),曾想过在副本地图中画些寻路点(即障碍物旁边可以起到诱导饶过障碍物的点),当主角碰到障碍物时,寻找副本地图中离自己最近的寻路点作为临时目标进行移动,到了此临时目标后再向最终目标移动,这样就可以巧妙饶过障碍物了。当然这在不太复杂的地图中是完全可行的,而且仿佛比A*更强悍且简单,但是一旦遇到复杂的地图了呢?我们该如何优化它?还需要对此感兴趣的朋友们开动一下脑筋,或许真能想出完美的解决方案呢。

      不管怎样,些许的缺陷是永远无法掩埋它的伟大的。连美工都可以轻松的参与到游戏地图引擎的主要设计中,难道不是游戏设计界中神圣的革新吗?快速开发是我们的理想与追求,只需要一张副本图片就可以较好的替代以往制作一个游戏地图引擎必须的四大步骤:切割、拼图、编辑、转换。是不是很邪恶?简化了过程却实现同样的效果,让人觉得措手不及!这就是21世纪!YEAH一个。

   下一节我将就主角与地图及其他对象进行相对移动进行详细讲解,敬请关注。

WPF/Silverlight
作者:深蓝色右手
出处:http://alamiye010.cnblogs.com/
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。
posted on 2009-06-17 21:17  深蓝色右手  阅读(12997)  评论(31编辑  收藏  举报