纸娃娃系统,或许大家听起来并不陌生。早在十几年前,当时不论是文字游戏“泥巴(Mud)”或是交友、社交网站,我们只能通过屏幕上的文字来传达与交互信息;随着技术不断进步,2D/3D图形技术高速崛起,通过在基础模型上由客户随意挑选、任意更换各种造型(素材),即可打造出真正属于“自我”独特风格的网络虚拟形象,QQ秀便是我们耳熟能详的代表,更贴近真实的如(RPG)游戏及虚拟现实中的换装/换肤系统同样亦得益于纸娃娃机制。

本节,我将向大家讲解如何最好的实现Silverlight 2.5D网络游戏中的纸娃娃系统,以最大程度控制性能损失为前提,将游戏资源占用最小化,综合效果及用户体验最优化。

以《Silverlight MMORPG网页游戏开发课程(Game Lesson)一期》的源码为基础,我将其再一次的进行了大规模重构。

素材来源于网络,取《封神榜3》中的角色系统(纸娃娃系统)做示例,每个角色大致都包含3个部件:铠甲(身体)、武器、骑乘(乘)等,而其中的骑乘道具又由2个部份组成,比如异人(弓手)的翅膀分为左右两支;甲士(战士)的坐骑分为前后两半;而方士(法师)的飞剑则仅为单独对象:

2D/2.5D游戏中角色带翅膀飞行要考虑左右翼与身体的层次关系,骑马则需要考虑马头/马尾与身体间的层次问题。而且武器长短,角色朝向,行为姿势等也都可能影响到各部件的层次关系。因此,一些游戏为了简化设计,同时又不失华丽,便诞生了比如“踏云”,“御剑”,“乘鹤”,“踩蝶”等诸多天马行空的驾驭模式,这些乘具的共同点就是均被踩在脚上,自然而然处理起来更简单明了。当然,如果角色是3D模型的话则无需考虑这么多层叠关系。

鉴于以上的参考分析,在Silverlight中构造装备纸娃娃系统框架便会轻松很多。暂时以带翅膀的弓手为例子,依葫芦画瓢,我们首先新建如下几个类:

如图,EquipBase乃装备(纸娃娃)系统中的核心,所有的装备部件类比如铠甲(身体)Armor/武器Weapon/翅膀Wing/坐骑Ride均继承自该类:

/// <summary> /// 装备部件基类 /// </summary> public abstract class EquipBase : ObjectBase { /// <summary> /// 加载完毕 /// </summary> public event EventHandler Ready; /// <summary> /// 获取或设置部件名 /// </summary> protected string partName { get ; set ; } long index = ; // 异步加载与换装同步协调 public override int Code { get { return base .Code; } set {
                index
++ ; if (value == - 1 ) { base .Code = value; return ; } string key = string .Format( " {0}{1} " , partName, value); if (Res.ContainsKey(key)) { base .Code = value;
                    loadConfig(key);
                }
else {
                    Downloader downloader
= new Downloader();
                    downloader.OpenReadCompleted
+= new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
                    downloader.OpenReadAsync(
string .Format( " {0}{1}.xap " , partName, value), string .Format( " {0},{1} " , index, value), 2000 );
                }
            }
        }
void webClient_OpenReadCompleted( object sender, OpenReadCompletedEventArgs e) {
            Downloader downloader
= sender as Downloader;
            downloader.OpenReadCompleted
-= webClient_OpenReadCompleted; string [] str = e.UserState.ToString().Split( ' , ' ); if (Convert.ToInt64(str[ ]) == index) { int code = Convert.ToInt32(str[ 1 ]); string key = string .Format( " {0}{1} " , partName, str[ 1 ]); if ( ! Res.ContainsKey(key)) { Res.Add(key, new StreamResourceInfo(e.Result as Stream, " application/binary " )); } base .Code = code;
                loadConfig(key);
            }
        }

Dictionary < string , Point > frameOffset = new Dictionary < string , Point > (); // 各帧偏移 /// <summary> /// 加载配置 /// </summary> void loadConfig( string key) {
            XElement info
= XElement.Load(Application.GetResourceStream(Res[key], new Uri( " Info.xml " , UriKind.Relative)).Stream).DescendantsAndSelf(partName).Single();
            FullName
= info.Attribute( " FullName " ).Value; // 解析各帧偏移 IEnumerable < XElement > iFrame = info.Element( " Frames " ).Elements();
            frameOffset.Clear();
foreach (XElement element in iFrame) {
                frameOffset.Add(element.Attribute(
" ID " ).Value, new Point() {
                    X
= ( double )element.Attribute( " OffsetX " ),
                    Y
= ( double )element.Attribute( " OffsetY " ),
                });
            }
if (Ready != null ) { Ready( this , null ); }
        }
bool _IsTurn; /// <summary> /// 获取或设置是否水平翻转 /// </summary> public bool IsTurn { get { return _IsTurn; } set { if (_IsTurn != value) {
                    Transform
= (_IsTurn = value) ? scaleTransform : null ;
                }
            }
        }
bool _Flash; /// <summary> /// 获取或设置是否闪光 /// </summary> public bool Flash { get { return _Flash; } set { if (_Flash != value) { // if (_Flash = value) { // dispatcherTimer.Start(); // } else { // this.Opacity = 1; // dispatcherTimer.Stop(); // } this .Opacity = (_Flash = value) ? 0.4 : 1 ;
                }
            }
        }
bool order = false ;
        DispatcherTimer dispatcherTimer
= new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds( 100 ) }; // 换装时的闪光特效计时器 public EquipBase() {
            dispatcherTimer.Tick
+= new EventHandler(dispatcherTimer_Tick);
        }
void dispatcherTimer_Tick( object sender, EventArgs e) { if (order) { this .Opacity = this .Opacity + 0.1 ; if ( this .Opacity >= 1 ) { order = false ; }
            }
else { this .Opacity = this .Opacity - 0.1 ; if ( this .Opacity <= 0.3 ) { order = true ; }
            }
        }
static Dictionary < string , Stream > equipRes = new Dictionary < string , Stream > ();

ScaleTransform scaleTransform = new ScaleTransform() { ScaleX = - 1 }; /// <summary> /// 呈现帧图 /// </summary> public void Display( string key) { string resKey = string .Format( " {0}{1}{2} " , partName, Code, key); if ( ! equipRes.ContainsKey(resKey)) {
                equipRes.Add(resKey, Application.GetResourceStream(Res[
string .Format( " {0}{1} " , partName, Code)], new Uri( string .Format( " {0}.png " , key), UriKind.Relative)).Stream);
            }
this .StreamSource = equipRes[resKey]; this .InternalOffset = frameOffset[key]; if (IsTurn) { scaleTransform.CenterX = Center.X - frameOffset[key].X; }
        }
public override void Dispose( object sender, EventArgs e) {
            dispatcherTimer.Stop();
            dispatcherTimer.Tick
-= dispatcherTimer_Tick; base .Dispose(sender, e);
        }
    }

内容比较简明:当角色需要换装时,通过异步下载的方式获取该装备部件的XAP包,一旦下载完毕便将其缓存起来使用。当然,由于是异步,在整个Loading的过程中为了提高用户体验,我们可以在角色(Role)身上做些修饰以让玩家一看就明白该角色正处于换装Loading,比较有新意的做法是在角色中心添加一些描述性的文字,或使用一些旋转类的动画(Animation)

另外,我还为其增加了一个名为Flash的方法,即当某个装备部件正处于Loading过程中时,该部件将执行时隐时现的Opacity动画,这种效果最完美了。不过,就目前的Silverlight 4 来说还无法对UIElementOpacity进行GPU硬件加速,暂时该方案的拓展与取舍/取代问题只能交由大家一同探讨。

然后是关于换装系统中的素材资源的组织。对于像Silverlight这样基于动态加载的游戏开发技术来说,最大程度减少质量损失前提下的资源容量高度浓缩有利于网页游戏的动态加载,以及像Windows Phone这样磁盘空间相对较小的移动设备平台。以精致的2.5D网游中的角色为例,大都以8方向居多,当然我们也还是能够仅仅使用5个方向素材即达到减少资源开支的效果(比如对其中的东北、东、东南进行水平翻转)

此方法以牺牲少量性能进行图像水平翻转为代价达到让资源总量减少近一半,且画质不打折扣的效果。唯一缺陷就是武器永远处于同一只手中,无论面朝何方;不过就整体而言,这不失为大多数网页游戏之首选。另外,对于Silverlight开发2.5D网页游戏来说,将图像资源PNG8化确实必要而关键。由于本节源码中的素材均来源于网络,所以效果很一般,如果是由3D美术原创的话,将逐帧图像导出并处理成颜色过渡均匀,边线条纹清晰流畅且无镂空的PNG8精美素材并非难事,最终还能再一次大幅降低游戏整体资源占用及内存开销:

此时,大家应该有注意到本节中的资源命名规范与以往有了些变化,形如a-b-c-d.png的形式,对于铠甲(身体)和武器来说,a代表状态(打坐/步行/骑乘)b代表行为动作(停止/移动/攻击/受伤)c代表朝向;d代表帧号。而对于骑乘道具,比如翅膀和坐骑,a代表行为动作;b代表对象代号(比如翅膀1/翅膀2,坐骑前半部分/坐骑后半部分)cd则与前面一致。当然,这或许仅符合我个人的思维习惯,自认为如此配置更便于理解和使用,还是那句老话,只要能给程序的编写带来便利,依旧是仁者见仁,智者见智,并无定论。

当装备类及相关资源设置完毕后,我们便可通过一个Role控件作为容器进行统一包装管理,此时我们创建一个名为RoleBase的角色基类,游戏中一切主体生命对象均由此衍生而来,比如英雄(Hero)/怪物(Monster)/非控对象(NPC)等等:

大伙应该会留意到,与以前编写的结构有所不同,此时的Sprite的意义得到了更广泛的延伸,是一次新的诠释,它指代所有基于场景坐标系布局中的对象(映射到现实世界中即指一切活动着得对象),比方说角色(如英雄,怪物,宠物,动物,NPC,动画,魔法等),道具(如火焰,植物,飞箭等),特效(如云雾缭绕,打雷闪电,刮风下雨,花叶纷飞)等等,我们均可将其纳入“游戏精灵”的行列。外加上对角色的Coordinate(场景中的Point坐标属性)Position(游戏画布中的Point坐标属性)进行了更完美的协调,于是整个游戏控件项目(Controls)重构后层次关系更趋合理,耦合度降低,重用性更高,更利于后期功能的拓展。

最后还是得特别强调下,Silverlight游戏中尽量使用小尺寸图片,因为图像的尺寸越大越消耗UI线程。作者曾经尝试过对英雄的4个部件均使用510*510尺寸的帧图像,即精灵每动一下就会同时切换4510*510的图片;此时同屏仅共存10个该英雄便已让CPUFPS痛苦不堪;而如果将该4个部件的每张图像多余的透明部分裁剪掉,即每张帧图片均只有不到100的宽和高,然后通过TranslateTransform偏移到共同位置上,性能较之前几乎提升了几十个数量级,同屏1004件套精灵FPS照样不下30,开发者们切记了:

本节源码请到目录中下载

在线演示地址:http://silverfuture.cn/

原文链接: http://www.cnblogs.com/alamiye010/archive/2011/02/22/1961817.html

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

Silverlight 2.5D RPG游戏技巧与特效处理:(二)纸娃娃系统相关推荐

  1. Silverlight 2.5D RPG游戏技巧与特效处理(Game Effects):目录

    以当下主流的2.5D RPG客户端品质游戏特效为借鉴,以最大程度控制性能损失为前提,将Silverlight游戏资源占用最小化,综合效果及用户体验最优化,即本系列作者想要向大家讲解的核心技术知识. 本 ...

  2. Silverlight 2.5D RPG游戏技巧与特效处理:(二十一)自定义路径动画

    一直在想应该用什么作为<Silverlight 2.5D RPG游戏技巧与特效处理系列教程>的终结,既要实用而不拖泥带水:又要通用而不哗众取宠.于是一不小心便成就了我一个未了心愿:一切基于 ...

  3. Silverlight 2.5D RPG游戏技巧与特效处理:自定义路径动画

    一直在想应该用什么作为<Silverlight 2.5D RPG游戏技巧与特效处理系列教程>的终结,既要实用而不拖泥带水:又要通用而不哗众取宠.于是一不小心便成就了我一个未了心愿:一切基于 ...

  4. Silverlight 2.5D RPG游戏技巧与特效处理:(十一)AI系统

    Silverlight 2.5D RPG游戏技巧与特效处理:(十一)AI系统 作者: 深蓝色右手  来源: 博客园  发布时间: 2011-04-19 11:18  阅读: 1282 次  推荐: 0 ...

  5. Silverlight 2.5D RPG游戏技巧与特效处理:(三)动态光影

    通常来说,只要谈到影子及影子制作,首先想到的不外乎3D.游戏中的影子设计大致可分为硬实现和软实现两种,比如像"游戏影子制作技术"这篇文章所谈到3D游戏影子制作方案Projectiv ...

  6. Silverlight 2.5D RPG游戏技巧与特效处理:(五)HLSL渲染动画

    或许大家依旧对上一节中的"黑夜"及"梦回过去"记忆犹新,追问下去HLSL到底是何方神圣能实现如此炫酷之效果?HLSL(高级着色器语言)作为微软的独门兵器,仅供D ...

  7. Silverlight 2.5D RPG游戏技巧与特效处理:HLSL渲染动画

    或许大家依旧对上一节中的"黑夜"及"梦回过去"记忆犹新,追问下去HLSL到底是何方神圣能实现如此炫酷之效果?HLSL(高级着色器语言)作为微软的独门兵器,仅供D ...

  8. 一起谈.NET技术,Silverlight 2.5D RPG游戏技巧与特效处理:(五)圣赞之HLSL渲染动画...

    或许大家依旧对上一节中的"黑夜"及"梦回过去"记忆犹新,追问下去HLSL到底是何方神圣能实现如此炫酷之效果?HLSL(高级着色器语言)作为微软的独门兵器,仅供D ...

  9. Silverlight 2.5D RPG游戏技巧与特效处理:(十二)魔法系统

    全球首款Silverlight – MMORPG:<<窝窝世界>>震撼登场!伴着与XNA合体后的Silverlight 5 强势发布,一波Silverlight网游研发海啸即将 ...

  10. 一起谈.NET技术,Silverlight 2.5D RPG游戏技巧与特效处理:(十二)魔法系统

    世界首款Silverlight – MMORPG:<<窝窝世界>>震撼登场!伴着与XNA合体后的Silverlight 5 强势发布,一波Silverlight网游研发海啸即将 ...

最新文章

  1. 智能手机触摸屏失灵和触摸屏保养教程
  2. 数据结构第5章例题 若矩阵Am×n中存在某个元素aij满足:aij是第i行中的最小值且是第j列中的最大值,则称该元素为矩阵A的一个鞍点。试编写一个算法,找出A中的所有鞍点。
  3. 【笔记】windows10安装linux双系统教程(可能是现今最简单方法)
  4. 【干货下载】大数据分析的四个关键环节
  5. Android Input 子系统初探
  6. Atitti html5 h5 新特性attilax总结
  7. 怎样在计算机上注册dll文件,win10如何注册dll文件_win10系统dll文件怎样安装
  8. texlive的安装
  9. 计算机PS个人规划目标,这里有一份PS个人陈述写作规划
  10. 需求分析-4 用例分析
  11. VIEW: X$KSMLRU - LRU flushes from the shared pool - (7.3 - 8.1) [ID 43600.1]
  12. vbs打开网页并全屏
  13. Flash 实验 飞机爆炸
  14. 蓝牙耳机什么样式最舒服?推荐一款好用的蓝牙耳机
  15. YS_20190822_图像_Matlab_04
  16. 单点故障解决方案介绍smart link/monitor link /stp
  17. win2012/2008取消禁ping
  18. rasa算法_【RASA系列】语义理解(上)
  19. iOS之nib、xib及storyboard的区别
  20. 利用for循环打印 9*9 表?

热门文章

  1. 【1】PASCAL VOC数据集下载
  2. dell安装linux系统网卡,DELL 2850服务器Redhat Linux 9系统安装网卡驱动记
  3. 一个朋友的精彩BLOG
  4. php smarty 模板注释,smarty中的注释
  5. php adodb类库下载,PHP_PHP程序中使用adodb连接不同数据库的代码实例,adodb.inc.php文件需要去下载一个 - phpStudy...
  6. 获取自身进程结构和csrss.exe进程结构 explorer.exe进程结构
  7. 提问的智慧(转自github)
  8. Flutter Android权限问题
  9. vs2015水晶报表(Crystal Reports)连接Oracle11g
  10. 计算大数阶乘--VB Script 版