是否期待了很久?本节就来个重量级的做为开场白吧:主位式地图移动模式。何谓主位式地图移动模式,即以主角为中心,它的移动带动着所有对象包括地图、物体对象、其他玩家、怪物等等的相对移动,这些对象的移动都是以主角为参照物的。最典型例子莫过于当前流行的MMORPG了,你控制的角色在地图中永远是处于窗口正中心的位置(除了8个角落外),这就是主位式地图移动模式(如下图)。

有朋友开始焦躁了:我的妈呀,才刚学完牵引式地图移动模式,还没完全吸收呢,又来个什么鬼主位式地图移动模式,头都大冽!

其实一点也不用担心,一个结构贼清晰的程序只需要您一次轻轻的手术即可以实现功能的革新。是否对谦谦的魔术记忆犹新?神奇的时刻即将降临,Let me show you the principle first:

游戏窗口尺寸仍然暂定为800 * 600,如上图,我将游戏地图(尺寸1750 * 1440)分为9个部分;当主角处于左上(Spirit.X<=400 && Spirit.Y<=390)、右上(Spirit.X>=950 && Spirit.Y<=390)、左下(Spirit.X<=400 && Spirit.Y>=1050)、右下(Spirit.X>=950 && Spirit.Y>=1050)这4个角落区域时,地图静止,主角如第九节中的一样可以任意在窗口中移动,直接讲就是主角在窗口中的显示位置即是它的图片左上角点(X-CenterX,Y-CenterY);

当主角处于中上(Spirit.X>400 && Spirit.X<950 && Spirit.Y <=390)、中下(Spirit.X>400 && Spirit.X<950 && Spirit.Y >=1050)这2个边缘区域时,主角在水平方向上始终居中,移动时它在窗口中只会做上下移动,水平方向上通过地图相对反向移动形成主角水平方向前进的视觉效果;

当主角处于左中(Spirit.>390 && Spirit.Y<1050 && Spirit.X<=400)、右中(Spirit.>390 && Spirit.Y<1050 && Spirit.X>=950)这2个边缘区域时,主角在垂直方向上始终居中,移动时它在窗口中只会做左右水平移动,垂直方向上通过地图反向移动形成主角垂直方向前进的视觉效果;

当主角处于正中区域,也就是游戏中主角最多的时候,主角此时始终处于游戏窗口的正中位置(定位到脚底),当它移动时,在窗口中通过地图的反向移动从而在视觉上形成主角在移动(实际上主角是静止的,只做方向动画移动),这与第二十节中的牵引式地图移动模式有异曲同工之处,只是两者刚好相反:前者主角不动,地图反向移动;后者为地图随鼠标的牵引而移动,主角不动。最后得出结论:我们只需将第二十节中的AllMove()方法进行修改,即可以实现完美的模式转换。

原理就这么简单,至于其中的数字是如何得到的,我们只需要预先定义好两个参数WindowCenterX,WindowCenterY。它们其实就是游戏窗体尺寸的5折(如果有标题栏则需要减去标题栏的高度约20像素),以800*600的窗口模式游戏窗体为例,那么它的WindowCenterX=800/2 =400,WindowCenterY=(600-20)/2=290,那么1024*768呢?以此类推。理请了思路,接下来就让我们来实现主位式地图移动模式下的AllMove()方法,这里我以主角位于左上这个区域为例:

private void AllMove() {

if (Spirit.X <= WindowCenterX && Spirit.Y <= WindowCenterY) {

//地图左上

//所有精灵以主角为参照相对移动

for (int i = 0; i < Carrier.Children.Count; i++) {

if (Carrier.Children[i] is QXSpirit) {

//假如子控件为精灵类型,则获取之

QXSpirit spirit = Carrier.Children[i] as QXSpirit;

//设置精灵在游戏窗口中的显示位置

Canvas.SetLeft(spirit, spirit.X - spirit.CenterX);

Canvas.SetTop(spirit, spirit.Y - spirit.CenterY);

//画家方法,使所有精灵之间的遮挡关系由近及远

Canvas.SetZIndex(spirit, Convert.ToInt32(spirit.Y);

} else if (Carrier.Children[i] is Image) {

//假如是地图/遮罩

Image Map = Carrier.Children[i] as Image;

Canvas.SetLeft(Map, 0);

Canvas.SetTop(Map, 0);

}

}

}

……

}

我们首先判断主角是否在左上的区域(Spirit.X <= WindowCenterX && Spirit.Y <= WindowCenterY),如果是,那么我们循环遍历画布中的所有子控件,假如某个控件是精灵类型(QXSpirit),那么我们捕获它。由于此时主角处于的是地图左上区域,按我们前面的分析,它在此区域内的显示位置就是它的坐标减去中心点值(CenterX,CenterY),因为精灵坐标是定位到脚底的,而窗口显示它的位置时是定位到精灵图片左上角点的。那么其他方向以次类推(源码中有这里就不再列罗列)。

做到这,有朋友忍不住要问了:

对于遍历子控件,我可拿手了,用Foreach不是更能胜任,为何还要用老土的For呢?

深蓝色:这涉及到在Foreach中动态添加和删除子控件的问题。举个最简单的例子,游戏中有一个怪物(monster),你一个如来神掌不小心把它给挂了(monster.Life=0),那么画布就需要对其控件进行移除(Carrier.Children.Reomve(monster));好,此时问题来了,Carrier.Children这个Collection集合的内容发生了变化(少了一个monster),这将导致系统十分的不高兴:*的!谁动了我的怪!(抛出InvalidOperationException异常),这就是臭名昭著的在Foreach遍历中由于对Collection内容进行更改而引发的血案!如何屏蔽它?用Try{}Catch{}?我非常拒绝在我的代码中出现这对兄弟,还剩下谁?惟有善良且和谐的For能肩此重任。

又有朋友问了:我们先判断了子控件是否为QXSpirit类型,恩,这很好很强大;但是后面接着将地图和遮罩当作Image来判断是不是有些太牵强?

深蓝色:嘿嘿!等你多时了。伟大的地图控件华丽登场:

有了第十四节关于创建精灵控件的知识,这地图控件只需要依葫芦画瓢,整一个轻松。那么我们依照第十四节中创建QXSpirit控件的方法,在Controls文件夹上点右键添加一个用户控件,取名叫QXMap

并为其添加如下属性:

// 地图关键点X定位到左上角>

public int CenterX { get; set; }

// 地图关键点Y定位到左上角0

public int CenterY { get; set; }

// 地图X坐标

public double X { get; set; }

// 地图Y坐标

public double Y { get; set; }

// 地图宽

public double Width_ { get; set; }

// 地图高

public double Height_ { get; set; }

// 地图图片源

public ImageSource Source { get; set; }

// 地图透明度

public double Opacity_ { get; set; }

由于地图与遮罩拥有几乎一样的属性,因此为了简单且统一化,我只建立一个名为QXMap的控件进行实现(当然,您将之分成QXMap和QXMask两个控件亦可),下文中为了区分,我均称地图为地表图层(简称地表),遮罩为遮罩图层(简称遮罩),这样可以让大家更好的理解QXMap的不同实现。回到它的属性上,其中的X,Y代表坐标,如果是地表则为,因为它自己相对于自身的坐标当然是(0,0);如果是遮罩,那么它的X,Y则是它图片左上角位于地表中的坐标。CenterX,CenterY目前暂时不会用到,因此均默认为即可;至于其他属性都很好理解这里就不再讲解。

地图控件创建完成,接下来我们将原先的:Image Map = new Image();用QXMap MapSurface = new QXMap();代替,Image Mask = new Image(); 用QXMap Mask = new QXMap();代替,并设置好相应的属性,这样就完成了通过地图控件对地表与遮罩的初始化。

到此,第二位朋友的问题已经云开见日,我们只需轻轻一扫键盘:

……

else if (Carrier.Children[i] is QXMap) {

//假如是地图/遮罩

QXMap Map = Carrier.Children[i] as QXMap;

Canvas.SetLeft(Map, 0);

Canvas.SetTop(Map, 0);

}

……

这样完美多了不是,嘿嘿,得瑟一下。

深蓝色!我还有问题!

更加深邃了我心中的理念:青春就是热血与激情!

深蓝色!我发誓这是最后一个问题:

你前面不是说游戏后期还会加入怪物(monster)、NPC(npc)等乱七八糟的东西,那么在判断的时候不是要这样写:

……

for (int i = 0; i < Carrier.Children.Count; i++) {

if (Carrier.Children[i] is QXSpirit) {

……

} else if (Carrier.Children[i] is QXMap) {

……

} else if (Carrier.Children[i] is QXMonster) {

……

} else if (Carrier.Children[i] is QXNpc) {

……

}

}

这不是没完没了了呀?而且这还是左上区域的实现代码,还有其他8个区域呢?维护起来不成了是典型的牵一发而动全身?

不提我还真差点给忘记了,如何将这些对象物体控件进行一个归类呢?分析:首先这些控件均为用户控件,用户控件继承自UserControl类;这道好了,在C#中只能单类继承,UserControl类在用户控件出生的时候就已经将这个尊位给踞为己有,哎,杂办可好??郁闷之时,接口天籁般的魔音再次缭绕于我的耳边:老大,还有我们捏!对呀!差点把软哥赐予我们神圣的接口姐妹给忘了。使用接口即可以对这众多的对象物体用户控件进行规范,又能被类一对多继承,很酷不是吗?

那么接下来我们添加一个接口取名叫:QXObject.cs,并对其进行如下设定:

interface QXObject {

int CenterX { get; set; }

int CenterY { get; set; }

double X { get; set; }

double Y { get; set; }

……

}

如此一来,只要对继承此接口的类设定好如上属性,再对现有的QXSpirit与QXMap两个控件添加对此接口的继承:

public partial class QXSpirit : UserControl, QXObject { …… }

public partial class QXMap : UserControl, QXObject { …… }

最后再次对前面的方法进行如下修改:

……

for (int i = 0; i < Carrier.Children.Count; i++) {

if (Carrier.Children[i] is QXObject) {

QXObject Object = Carrier.Children[i] as QXObject;

Canvas.SetLeft(Object, Object.X - Object.CenterX);

Canvas.SetTop(Object, Object.Y - Object.CenterY);

Canvas.SetZIndex(Object, Convert.ToInt32(Object.Y));

}

}

……

忽忽,大功告成!

当我们将AllMove()的9区域代码均补充完整后,替换掉第二十节中的AllMove()方法,其他的代码一个也不用改,结果就像变魔术一样,地图的移动模式转眼由牵引式地图移动模式转变成主位式地图移动模式,地图、遮罩、就连障碍物都同样的被无缝移植了,这难道不是奇迹吗?欣赏一下自己的劳动成果吧:

瞬间的模式转换是否让大家感到措手不及,匆忙中让太多的代码与属性显得臃肿冗余且无章可循,那么下一节我将对本教程源码进行第一次大规模重构,从设计升华到艺术,这是每一位开发者无上的追求,敬请关注。

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

原文链接: http://www.cnblogs.com/alamiye010/archive/2009/07/04/1516763.html

转载于:https://my.oschina.net/chen106106/blog/43621

C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十一)主位式地图移动模式...相关推荐

  1. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(十一)地图遮罩层的实现

    前面的章节主要针对地图表现层进行讲解.通常来说,简单的游戏光有它就足够了:但是为了达到更加真实的光影效果,模拟真实的虚拟世界,我们还得继续在地图上下大工夫.本节将就如何实现地图中的遮罩层,即物体对角色 ...

  2. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):目录

    本系列教程的示例代码下载(感谢 银光中国 提供资源分流): 第一部分源码:WPFGameTutorial_PartI(1-20节) 第二部分源码:WPFGameTutorial_PartII(21-2 ...

  3. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二)让物体动起来②

    第二种方法,CompositionTarget动画,官方描述为:CompositionTarget对象可以根据每个帧回调来创建自定义动画.其实直接点,CompositionTarget创建的动画是基于 ...

  4. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(一)让物体动起来①

    序:自从QXGame(WPF GAME ENGINE)游戏引擎公布以来,受到很多朋友的热切关注,于是乎有了写教程的想法.那么从今天开始,我将带领大家一步一步的学会如何使用纯C#开发WPF/Silver ...

  5. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(十八) 完美精灵之八面玲珑(WPF Only)②...

    紧接着上一节,首先得解释一下为什么需要将这272张图片合成为一张大图.因为如果游戏中还有装备.坐骑等其他设置,那么我们就需要对图片源进行时时的合成:同时对272张甚至更多的图片进行合成效率高还是对2张 ...

  6. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十五)完美捕捉精灵之神器 -- HitTest...

    怪物们都出现了,如何选中自己心仪的怪是主角目前首要做的事. 为了进行鼠标状态区别,我首先对鼠标变化规则进行约束:当鼠标在屏幕上空旷地图区域移动时,鼠标光标形态表现为默认光标 (0号光标图片),当鼠标经 ...

  7. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(四)实现2D人物动画①

    通过前面的学习,我们掌握了如何动态创建物体移动动画,那么接下来我将介绍WPF中如何将物体换成2D游戏角色,并通过使用前面所讲的DispatcherTimer计时器来实现2D人物角色的各种动作动画. 动 ...

  8. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(四十九) 落雷!治疗!陷阱!连锁闪电!多段群伤!魔法之终极五重奏②...

    本节,我将完成本教程示例游戏的最终两个魔法:传说中的连锁闪电与暴风雪.如此经典与华丽的家伙无论在哪款好游戏中都少不了它们的踪影. 首先是连锁闪电,在<英雄无敌>中体现得尤为出色,击中一个怪 ...

  9. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十六)通用型角色头像面板...

    目前游戏的开发进度已经基本实现了精灵对象之间的普通交互,接下来我们需要朝着实现战斗系统的目标前行.而实现它的前提是必须完善精灵控件的基本属性,如添加生命值.魔法值.活力值.经验值等基本属性并通过窗体界 ...

  10. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(四十七)远距离单体攻击与单体魔法...

    到目前为止,主角能使用的魔法均为群攻型魔法,群攻魔法的原理相对简单,常见如圆形范围,矩形范围,扇形范围等等,当魔法释放后可以按照本教程的做法对所有坐标处于相应范围内的怪物进行伤害处理,这是直观的处理方 ...

最新文章

  1. Windows下安装OpenSSL及其使用
  2. 软件:分享9款实用电脑软件,值得看一看
  3. spark on yarn 完全分布式_「大数据」(七十一)Spark之架构介绍
  4. js 请求接口获取不到登录cookie xhrFields 配置
  5. mybatis自动生成mapping和实体
  6. APP动态界面设计使用的利与弊
  7. c语言成绩查询系统_如何用Excel制作成绩查询系统-Leo老师
  8. IPXX防护等级中关于防水实验的规定
  9. RGBLCD显示实验————复习到这
  10. 坚果种类和营养价值排名
  11. 自制STC12C5A60S2最小系统板
  12. 西门子200smart与电流表Modbus RTU通讯
  13. JS实现点击跳转登陆邮箱
  14. 抖音推荐算法的底层逻辑,互动率包含什么指标?为什么它这么重要?
  15. 天数怎么换算成月_excel表中,怎么把日期数转换成月份数呢?
  16. 深度学习顶会论文投稿策略7步走(附资料)
  17. JAVA 导出Excel 单元格合并
  18. python编程 迷你世界_迷你世界迷你编程
  19. golang力扣leetcode 322.零钱兑换
  20. 基于骨骼点特征的视频分割任务——花样滑冰视频的动作解析

热门文章

  1. 入门人工智能 ——使用 tensorflow 训练一个新闻分类模型(6)
  2. CodeForces 1A-Theatre Square【数学】
  3. 嵌入式实时操作系统篇--I2C总线下PCF8574模块
  4. 【java华为机试】HJ14 字符串排序
  5. Made In Heaven(A*算法初步学习)
  6. react论坛问答demo(react+redux)
  7. 空气中散发着淡淡的清香
  8. 有关python学习的小建议
  9. 于永正《杨氏之子》第二课时课堂实录
  10. mysql tode_有用的mysql语句