专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »DotNet » wpfsilverlight:C#开发WPF/Silverlight动画及游戏系列教程(Game Course):( 2十) 第一部分拓展小结篇 »正文

wpfsilverlight:C#开发WPF/Silverlight动画及游戏系列教程(Game Course):( 2十) 第一部分拓展小结篇

来源: 发布时间:星期三, 2009年9月2日 浏览:74次 评论:0
  写了20节路向追着鬼子打样都没停过索性也想暂时休息下整理整理思绪好完成后面第 2部分更为精彩内容:诸如跟随式地图移动模式、NPC & 怪物 和主角互动、对象AI、攻击和魔法、各种类型伤害计算、完美RPG游戏界面……等等等等激动吗?讲实话:我很激动!

   读者声音:还没写就开始激动了典型傻子

   ^_^||言归正传本节就先来个承上启下小结吧我打算分4个部分对前20节内容进行补充拓展:

   、完美改进型A*寻路移动模式

   在C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(十 9) 完美精灵的 8面玲珑(WPF _disibledevent=>C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(十 2)神奇副本地图 结尾我曾轻描淡写叙述了如何实现改进型A*虽然通过副本地图简单实现了但是暂时并不完美那么下面我将向大家讲解通过地地道道思路方法实现改进型完美A*移动模式

   何谓改进型完美A*移动模式?即主角每次移动时首先并不启动A*寻路而是直接建立两点间直线移动;接下来即进行时时障碍物判断如果没有碰撞到任何障碍物或对象则将该直线移动保持到终点;但是中途旦碰到障碍物则以目地为终点即时启动A*寻路

   原理很简单关键技术就是如何对碰撞进行检测?

   传统思路方法有两种:

   第种我且称的为坐标还原法:即时时记录精灵未碰撞障碍物时坐标(Old_XOld_Y)在精灵移动时旦检测到精灵此时站到了障碍物上则将精灵此时坐标进行还原(X=Old_XY= Old_Y)然后启动A*寻路此思路方法优点是使用简单不需要复杂判断逻辑;缺点是效果不好在画面上将造成精灵瞬间被弹开情况虽然那刻非常短暂且距离微小但是对于精灵移动动画平滑性影响是严重因此我们最好不要采用此思路方法

   第 2种为启发式预测法:该思路方法原理为时时对精灵前方区域进行预测旦发现前方有障碍物则即时启动A*寻路直到目该思路方法可谓绝对皇室血统个字“正”集所有优点的大成者;优点多相对实现起来难度就大些在WPF/Silverlight中如何实现的?先来看下图:



   上图中已经给了很详细介绍说明即在直线移动过程中精灵时时判断此时朝向前方单元格是否为障碍物如果是则启动A*寻路饶过它充分理解了原理后我们可以通过如下思路方法来返回精灵是否将要遇到障碍物了:

//判断是否将要碰撞到障碍物(障碍物预测法)
private bool WillCollide {
  switch (()Spirit.Direction) {
     0:
        Matrix[()(Spirit.X / GridSize), ()(Spirit.Y / GridSize) - 1] 0 ? true : false;
     1:
        Matrix[()(Spirit.X / GridSize) + 1, ()(Spirit.Y / GridSize) - 1] 0 ? true : false;
     2:
        Matrix[()(Spirit.X / GridSize) + 1, ()(Spirit.Y / GridSize)] 0 ? true : false;
     3:
        Matrix[()(Spirit.X / GridSize) + 1, ()(Spirit.Y / GridSize) + 1] 0 ? true : false;
     4:
        Matrix[()(Spirit.X / GridSize), ()(Spirit.Y / GridSize) + 1] 0 ? true : false;
     5:
        Matrix[()(Spirit.X / GridSize) - 1, ()(Spirit.Y / GridSize) + 1] 0 ? true : false;
     6:
        Matrix[()(Spirit.X / GridSize) - 1, ()(Spirit.Y / GridSize)] 0 ? true : false;
     7:
        Matrix[()(Spirit.X / GridSize) - 1, ()(Spirit.Y / GridSize) - 1] 0 ? true : false;
     default:
        true;
   }
}


   WillCollide思路方法依据精灵朝向判断精灵前方是否为障碍物(即判断障碍物Matrix[,]此时是否为0)

   有了它以后我们同样还需要像C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(十 2)神奇副本地图 样建立个名为NormalMoveTo思路方法用于精灵直线移动此时我们只需要在第十 2节代码基础上增加精灵朝向部分即可:

        //直线移动
        private void NormalMoveTo(Po p) {
            //总移动花费
            totalcost = ()Math.Sqrt(Math.Pow(p.X - Spirit.X, 2) + Math.Pow(p.Y - Spirit.Y, 2)) / GridSize * UnitMoveCost;
            ……
            //创建主角朝向属性动画
            double direction = Super.GetDirectionByTan(p.X, p.Y, Spirit.X, Spirit.Y);
            doubleAnimation = DoubleAnimation(
              direction,
              direction,
              Duration(TimeSpan.FromMilliseconds(totalcost))
            );
            Storyboard.SetTarget(doubleAnimation, Spirit);
            Storyboard.SetTargetProperty(doubleAnimation, PropertyPath("Direction"));
            storyboard.Children.Add(doubleAnimation);
            //动画播放
            storyboard.Begin;
        }


   这里要特别注意是我用黄色背景注明totalcost这个变量值代表精灵在两点间移动所需要花费时间计算它Storyboard动画是基于时间轴动画(即在个规定时间内完成指定动画)节中也有相应介绍说明因此为了让精灵在全角度(不仅仅是8个方向是360度全方位)任意两点间直线移动时均使用统速度(每移动个单元格固定花费UnitMoveCost毫秒)这样不论两点间是30度、40度、55度、76.3度、87.6度等等随意多少角度精灵均能进行平滑均速移动

   OK切就绪接下来就是在游戏窗口中鼠标左键点击事件中启动精灵直线移动:

        private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            Po p = e.GetPosition(Map); //点击地方在Map中坐标点
            //假如点击地点不是障碍物
            (Matrix[()p.X / GridSize, ()p.Y / GridSize] != 0) {
                Spirit.Destination = p; //设置主角最终移动目
                Spirit.Action = Actions.Run; //主角动作切换成跑步状态
                Spirit.IsAStarMoving = false; //非寻路模式
                NormalMoveTo(p); //两点间建立直线移动
            }
        }


   看完上面代码有朋友就要问了:IsAStarMoving是什么东西?简单讲它是精灵直线移动和A*寻路移动枢纽虽然我们实现了NormalMoveTo和AStarMoveTo这两种移动方式但是如何在逻辑中对其进行很好衔接这里就必须加入IsAStarMoving这个精灵属性有了它我们就可以在窗口刷新事件中这样写:

        //游戏窗口刷新主线程间隔事件
        private void Timer_Tick(object sender, EventArgs e) {
            ……
            //判断主角是否移动到了目标如果是则动作切换成停止
            (ArriveTarget) {
                Spirit.Action = Actions.Stop;
            } (!Spirit.IsAStarMoving && WillCollide) {
                //在寻路移动模式中主角100%会饶过障碍物
                //因此只有在非寻路模式中才需要时时判断主角是否将要碰撞障碍物
                AStarMoveTo(Spirit.Destination);
                Spirit.IsAStarMoving = true;
            }
        }


   通过黄色背景代码部分逻辑我们可以轻松实现精灵直线移动和A*移动转换即精灵首先进行直线移动在它没有到达目地的前(ArriveTargetfalse)我们需要时时判断它是否将要碰撞到障碍物(判断WillCollide是否True)并且前提是精灵在此移动中还没启动过A*寻路(IsAStarMovingfalse)旦在直线移动中启动过A*寻路结果100%会引导精灵饶过障碍物到达终点A*寻路过程中不需要额外再判断是否还会碰撞到障碍物那是多此举(如果出现偶然不要怪别人怪自己没把A*算法写正确)如果此两个条件都符合了则以精灵移动目标(Destination)为终点启动A*寻路模式这样就顺利由直线移动转入到A*寻路移动完美衔接和枢纽



   2、完美遮罩层

   在C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(十)地图遮罩层实现 中我曾经讲解了如何实现地图遮罩层虽然是实现了但是还有些小小瑕疵如果不屏蔽它那么这会很大幅度影响到游戏画面效果

   首先我们按照第十节中说到思路方法来截取我们地图中心这个标志物并取名为Mask3.png并且加载到项目Map文件夹中:



   接下来我们同样在后台代码中化它:

        //创建遮罩层
        Image Mask = Image;
        private void InitMask {
            Mask.Width = 202;
            Mask.Height = 395;
            Mask.Source = BitmapImage(( Uri(@"MapMask3.png", UriKind.Relative)));
            Mask.Opacity = 0.7;
            Carrier.Children.Add(Mask);
            Canvas.SetZIndex(Mask, 612); //其中612 = Mask高 + MaskY值由于还没引进地图Control控件暂时这样写
        }


   最后就是在游戏窗口刷新线程中对它也进行时时更新:

private void Timer_Tick(object sender, EventArgs e) {
  ……
        //遮罩层跟随移动
        Canvas.SetLeft(Mask, MapLeft + 793);
        Canvas.SetTop(Mask, MapTop + 217);
        //主角跟随地图同时移动
        Canvas.SetLeft(Spirit, Spirit.X - Spirit.CenterX + MapLeft);
        Canvas.SetTop(Spirit, Spirit.Y - Spirit.CenterY + MapTop);
        Canvas.SetZIndex(Spirit, ()Spirit.Y); //时时更新它层次(画家算法)
        ……
     }


   这里关键代码就是黄色部分代码我通过Canvas.SetZIndex(Mask, 612)设置了遮挡物层次处于ZIndex:612这个位置弄过网页同志对Z-Index应该不陌生它们意义是最后还需要精灵ZIndex和的配合才能实现完美默契遮罩效果因此我们在线程中时时根据精灵Y坐标来更改它ZIndex:Canvas.SetZIndex(Spirit, ()Spirit.Y)这样就算以后增加了其他NPC、怪物的类对象物体只要同样设置它们ZIndex=Y即可以完美实现时时层次关系



   3、完美换装

   这个就简单多了的前章节中已经将相关参数和实现思路方法都定义好了那么剩下就是如何问题这里为了演示方便我在xaml里面添加了两个下拉列表(ComboBox)分别对应衣服及武器代号个换装启动按钮(本教程目录中有源码下载这里就不列出来了)然后我们在此按钮点击事件中只需要3行代码就可以轻松实现换装:

        //换装
        private void ChangeEquipment(object sender, RoutedEventArgs e) {
            Spirit.Equipment[0] = Convert.ToInt32(comboBox1.SelectionBoxItem.);
            Spirit.Equipment[1] = Convert.ToInt32(comboBox2.SelectionBoxItem.);
            Spirit.Source = Super.EquipPart(Spirit.Equipment, Spirit.DirectionNum, Spirit.DirectionFrameNum, Spirit.TotalWidth, Spirit.TotalHeight,Spirit.SingleWidth, Spirit.SingleHeight);
        }


   这 3行代码实在太简单因此不再多做介绍说明需要提是在更换衣服和武器时候如果内存中不存在该新衣服和武器搭配则需要时时进行合成这会根据您CPU速度来决定游戏卡壳时间毕竟是个较大量图片合成计算这在网络游戏中同样经常会遇到例如你是否有过这样经历:当你到了定级别可以更换更高级武器时你双击该武器时候游戏会卡住那么以下然后“哐铛”武器才安装上去;但是如果你再把这把武器取下再重新换上时却点也不卡这就是装备缓存Cache起到了作用



   4、A*寻路的大补充

   鉴于目前很多朋友反馈说A*难度过高而无法理解因此我打算就A*寻路使用及相关要点做次重大补充介绍说明:

   使用A*首先需要引用QX.dll集;接着在中创建IPathFinder PathFinder = PathFinderFast(Matrix);这里有个重要参数Matrix它是用来描述寻路坐标系中(即缩小后坐标系)障碍物 2维矩阵我们这样创建它[,] Matrix = [1024, 1024]; Matrix[x,y]和寻路坐标系中坐标是对应关系例如Matrix[150,266]即对应寻路坐标系中(150,266)这个点假设GridSize=10那么寻路坐标系中(150,266)此点即对应游戏窗口中Canvas.LeftProperty(1500)、Canvas.TopProperty(2660)并且如果该点是障碍物则我们设置Matrix[150,266]=0如果不是障碍物而是可以通行地点则我们设置Matrix[150,266]=1Matrix[,]其他所有点依此类推均设置后即完成了地图中障碍物布局这里出现了GridSize这个很重要概念它起到缩放坐标系作用如何来理解它呢?这里我以[,] Matrix = [1024, 1024]为例1024*1024像素地图仅仅是张大约我们个电脑桌面尺寸但是要在它上面构建障碍物却需要我们对1024*1024=1048576个点进行设置简单有规律障碍物布局还好要是遇到复杂地图该如何办?这还是小事要是地图尺寸为10000*10000像素(这在MMORPG中再常见不过了)它带来不仅是个大内存[,] Matrix = [10000, 10000]更可怕是在没有制作地图编辑器的前去设置其中100000000个障碍物简直就是件让人崩溃致极

   因此我引入GridSize(单位格尺寸)这个参数来对坐标系进行缩放操作从而达到简化地图构建过程例如我设置GridSize=20那么游戏坐标系中坐标都是窗口中坐标1/20例如窗口中Canvas.getLeft(Spirit)=123;Canvas.getTop(Spirit)=353;则对应游戏坐标系中(6,17)(可以直接用整数相除结果会取整数部分)这样张10000*10000地图只需要Matrix[500,500]来实现障碍物构造并且个角色占据个20*20尺寸单元格是非常合理以下为有关引入GridSize这个参数概念几大优势整理总结:

   1、简化障碍物并且使得地图构造伸缩自如:有关简化提高性能在上面已经说了至于伸缩自如是我只需要通过改变GridSize其他代码均不变即可以实现区别精度游戏坐标系不信?在前面章节中我GridSize均为20本节我将的设置成了10大家可以很明显看到障碍物精度提高了1倍(如下图):



   大家也不妨将GridSize分别设置成1、5、30等然后相应修改障碍物(区别GridSize下障碍物位置肯定区别)再运行看看在区别GridSize下游戏地图界面是但是单元格精度却是区别特别值得当GridSize=1时此时寻路坐标系窗口坐标系这或许也能让朋友们更好理解GridSize意义

   2、提高寻路算法速度例如我们设置GridSize=20此时在40*40像素地图区域内只有2*2=4个游戏坐标系单元格即(00)、(01)、(10)、(1、1);如果你需要让角色从区域左上角移动到右下角则只需要在这4个点内进行寻路计算出从点(00)到点(11)路径;而如果GridSize=1即不进行游戏坐标系中单元格缩放而以窗口中像素点作为基础单元格那么在40*40像素区域内有40*40=1600个坐标点;同样如果你需要将角色从区域左上角移动到右下角就必须在这1600个点内进行寻路计算出从点(00)到点(4040)路径因此我们将GridSize设置为<=20即不失定位精度又大幅度简化及优化地图构造及性能何乐而不为?



   3、SLG、回合制等类型游戏地图引擎制作中必定参数如果你说RPG(ARPG、MMORPG等)类型游戏肯定都有自己地图编辑器从而能轻松实现以像素为单位(精确度达到GridSize<=5)高精度障碍物构造及地图编辑这我100%赞同(前提:必须有地图编辑器否则后果就如我上文中提到张大且无规律地图将让你痛不欲生)但是在SLG、回合制等基于N*N尺寸基础单元格游戏中就如同它们往往被大家通俗描述为走格子(战棋类)游戏地图格子概念无处不在无论是垂直地图或是斜度地图通过设置GridSize都可以轻松将的实现

   归纳补充了那么多有关A*相关使用大家是渐渐进入状态了?

   本节即将结束同样标志着第 2部分开始第 2部分我将就本文开头用彩色字所提到相关知识进行讲解或许那才是您真正想要了解它将引领我们进入个真正2D游戏制作中敬请关注



0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: