Silverlight 2.5D RPG游戏技巧与特效处理:(十六)动态资源

即开即玩是网页游戏相比传统客户端游戏的最大优势。如果说在每台电脑安装上G的客户端是一种资源浪费及时间污染;那么Silverlight作为RIA界的新宠儿,在继承祖辈优秀血统的前提下拥有更加卓越的性能及更为曼妙的动态表现,势将引领网络未来世界进入那令人神往的低碳空间。

笔者学习Silverlight开发近2年,在写第一部Silverlight游戏系列教程时为了尽快的实现目标而将所有素材资源打包进XAP中。与其他Silverlight初学者一样,这或许是我们所必须会经历的一个过程。QXGameEngine最终完成时,它的体积已经达到了18M有余,功能需求满足了预期,可是却让大量还未接触过Silverlight的朋友产生巨大困惑:难道Silverlight仅仅是镶嵌于网页中的游戏客户端?

QXGameEngine作为教程示例再贴切不过;但如果说要将之商业化,首先就违背了RIA的初衷:即开即用。漫长等待是对用户体验的无情扼杀,不仅随时可能造成用户流失,毫不客气的尊称其为失败品亦不为过。

针对Silverlight资源配置问题,国外很多朋友首先想到且用得最多的莫过于独立存储(Isolated Storage)。比如Dark Ieign -- 最近在网站上看到的Silverlight2D即时战略大作。虽然其等待资源下载过程中我们可以通过欣赏游戏宣传动画短片打发时间,但本质却与QXGameEngine如出一辙,将所有的资源必须性的一次性下载完,不管会不会用到,这样的形式仍旧十分糟糕。是的,Silverlight才刚起步,毕竟Dark Ieign让我们看到的是一款大作风范。历史中新生事物的起源都必然会经历一个适应期,不久的将来一旦Silverlight完美动态技术普及开后,堪比星际争霸2之类大作终有一天会出现在Silverlight平台上,拭目以待!

8个多月过去,第一部游戏教程全部完成了。其后QXSceneEditor在笔者思考如何实现Silverlight游戏快速开发的同时孕育而生。其搭建于一个兼具静态资源及动态资源混合使用的游戏框架下,XAP包存放的不再是一切资源,而仅仅是一些常用的小图片、图标及场景、精灵等配置文件;相对于前作,该场景编辑器动态参数及动态配置的灵活结构可以轻松拓展出任意类型的各式游戏,而不仅仅再局限于RPG

又是3个月,第二部教程伴随着3个全新Demo的完成落下帷幕。此时再次重温QXSceneEditor,仔细琢磨又一次感到其结构仍不完美:一开始就加载所有场景及所有精灵的xml配置信息,假想一下如果有100个场景,而玩家或许从注册到对游戏失去兴趣也走不到10个场景,那剩下的90个场景的配置文件容量不是白下载了?林林种种……。随后的第三部课程虽然有了结构性的进步,然后更多的与第一部类似,着重在于基础学习。而后,在中游在线的《WOWO世界》的感悟下触使我决定再次去探求Silverlight-WebGame的极至框架,理想中它应该贯穿着“一切动态”,“按需加载”的搭建理念,秉持“体验至上”,“优异性能”的整体特性,于是诞生了想要从头来过的全新思路。

这是一次真正的从零开始,技术的革新让我决心从游戏的开始制作到游戏的结局,不在乎这个结局是喜是悲;于是有了这个全新的第四部作品,它们将倾注更多关于自己领悟的Silverlight-Web游戏设计思想。同以往一样,如果朋友们觉得有不对之处,恳请善意指正。这三个系列的诞生与发展不光是我一个人的努力,没有大家的支持、建议和批评,也不会坚持到今天。

以上抒情。

接下来将进入本节的主要内容:Silverlight WebGame中的动态资源配置。

JavascriptvarF#lambdaC#在取之精华,去其糟粕的同时让自身发展得更为完美,趋势中弥漫着“动态”给我们编程带来的无限芳香。“动态”,不论在任何场合都是一种优秀表现;与“动态”相呼应的是“自适应”,从布局的“自适应宽高”到游戏资源的“自适应按需下载”,这些均可以从当下诸多优秀的软件架构中得到充分体现。

Silverlight学习之初大家已意识到动态下载的重要性,从最初的探讨dll动态下载、xaml动态加载、xap动态获取到数据传输的序列化与反序列化以及资源的压缩与解压。直到今天,笔者在反复尝试下终于完成了个人感觉目前效果还算较好的资源结构布局模式:独立于对象的配置布局体系

何谓独立于对象的配置布局体系?我们不妨先看张图:

以游戏中的动画为例,素材布局以数字代号顺次标识,与传统不同的关键在于我为每个动画资源都配备了一个描述该动画信息的Info.xml配置文件,以上图0号动画为例,该动画的Info.xml信息如下:

<?xml version="1.0" encoding="utf-8" ?>

<Animation Width="400" Height="400" FrameNum="7" Interval="140" Format="1" Images="Animation/0,0-6,png"/>

    当游戏中某个场合需要演示该动画时,我们会首先下载该动画对应的Info.xml并进行解析,再将所有参数赋予自定义的如AnimationButton控件,从而实现动态呈现。

接下去的问题是我们如何下载该Info.xml配置文件,以及解析完成后如何实现队列下载所需的N张图片?另外,对于已经下载好的图象文件及xml配置文件我们该如何区别对待?

大家是否还记得在Silverlight游戏设计(Game Design)这部教程中,我为每个Demo都附加有一个后缀为. Tools的项目,该项目中除了A*寻路的方法类库外还包含一个资源下载用类Downloader。然而此下载器仅仅实现的是单个图象文件下载,为了满足任意文件下载需求,且与接下来的队列下载类所兼容,我这里的将之进行了如下修改:

    public sealed class DownloaderEventArgs : EventArgs {

        public string uri { get; set; }

        public Stream stream { get; set; }

    }

    public delegate void DownLoaderEventHandler(object sender, DownloaderEventArgs e);

public sealed class Downloader {

        /// <summary>

        /// 已下载的文件路径字典

        /// </summary>

        static Dictionary<string, bool> files = new Dictionary<string, bool>();

        /// <summary>

        /// 资源正在读取中

        /// </summary>

        public event DownLoaderEventHandler Loading;

        /// <summary>

        /// 资源下载完成时触发

        /// </summary>

        public event DownLoaderEventHandler Completed;

 

        DispatcherTimer timer;

        /// <summary>

        /// 通过WebClient下载资源

        /// </summary>

        public void GetResource(string uri) {

            //假如该路径图片还未下载过

            if (!files.ContainsKey(uri)) {

                WebClient webClient = new WebClient();

                webClient.OpenReadCompleted += (s, e) => {

                    //该路径图片已下载完成

                    files[uri] = true;

                    if (Completed != null) { Completed(this, new DownloaderEventArgs() { uri = uri, stream = e.Result }); }

                };

                webClient.OpenReadAsync(new Uri(uri, UriKind.Relative), uri);

                files.Add(uri, false);

                if (Loading != null) { Loading(this, new DownloaderEventArgs() { uri = uri, stream = null }); }

            } else {

                //假如该路径图片已下载完成

                if (files[uri]) {

                    if (Completed != null) { Completed(this, new DownloaderEventArgs() { uri = uri, stream = null }); }

                } else {

                    if (timer == null) {

                        //假如该路径图片正在下载,则需要等待,每隔1秒检测一次是否已下载完成

                        timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };

                        timer.Tick += (s, e) => {

                            if (files[uri]) {

                                if (Completed != null) { Completed(this, new DownloaderEventArgs() { uri = uri, stream = null }); }

                                DispatcherTimer t = s as DispatcherTimer;

                                t.Stop();

                                t = null;

                            }

                        };

                        timer.Start();

                    }

                    if (Loading != null) { Loading(this, new DownloaderEventArgs() { uri = uri, stream = null }); }

                }

            }

        }

    }

此时该类不仅能下载图象文件,同时还能将数据流中的Stream作为参数附给Completed事件;后面的事情就简单多了,在Completed事件中可以直接通过XElement xelement = XElement.Load(e.stream)对该xml文件进行加载,后续的步骤就不用再多说了吧。

剩下的就是解析完xml配置后该如何队列下载所需的一切图象资源。很多朋友一听到队列、按需就发慌,其实我们只要对上面重新编写的Downloader再进一步逻辑封装就OK了,这个神奇的DownloadManager类其实也不过如此嘛:

   public sealed class DownloadManager {

        public event EventHandler Completed;

        int uriNum, count;

        List<string> uris;

 

        /// <summary>

        /// 根据资源表下载资源

        /// </summary>

        /// <param name="uris">资源地址表</param>

        public void GetResource(List<string> uris) {

            if (uris.Count == 0) {

                if (Completed != null) { Completed(this, new EventArgs()); }

            } else {

                this.uris = uris;

                uriNum = uris.Count;

                DownloadResource(0);

            }

        }

 

        private void DownloadResource(int index) {

            Downloader downloader = new Downloader();

            downloader.Completed += new DownLoaderEventHandler(downloader_Completed);

            downloader.GetResource(uris[index]);

        }

 

        private void downloader_Completed(object s, DownloaderEventArgs e) {

            count++;

            if (count < uriNum) {

                DownloadResource(count);

            } else {

                if (Completed != null) { Completed(this, new EventArgs()); }

            }

        }

    }

再回到开头,以自定义呈现动画控件AnimationButton为例,在控件初始化后我们首先下载对应代号的动画xml配置文件:

Downloader downloader = new Downloader();

downloader.GetResource(Global.WebPath(string.Format("Animation/{0}/Info.xml", code)));

然后注册Completed事件,一旦完成后对配置文件进行解析并取值,比如:

downloader.Completed += (s1, e1) => {

                    string key = string.Format("Animation{0}", code);

                    if (e1.stream != null) {

                        Global.PackInfo.Add(key, XElement.Load(e1.stream));

                    }

                    XElement config = Global.PackInfo[key].DescendantsAndSelf("Animation").Single();

                    this.format = Global.FileFormat((Format)((int)config.Attribute("Format")));

                    this.frameNum = (int)config.Attribute("FrameNum");

                    this.Width = (double)config.Attribute("Width");

                    this.Height = (double)config.Attribute("Height");

                    ……

Waiting waiting = new Waiting(this.Width, this.Height) { Z = 999999 };

                    //下载动画资源

                    DownloadManager downloadManager = new DownloadManager();

                    downloadManager.Completed += (s2, e2) => { Heart.Start(); this.Children.Remove(waiting); };

                    downloadManager.GetResource(Global.GetImageList(config.Attribute("Images").Value));

      };

该事件中最后3行即实现了通过DownloadManager来获取配置文件中”Images”节点的属性值(Attribute("Images").Value)后解析并队列下载所需图片,此过程中我们可以先展示一个Waiting动画作为代替,当队列下载完成后(downloadManager.Completed)我们再对实际动画进行播放呈现:

Heart.Tick += (s, e) => {

                if (currentFrame == frameNum) {

                    switch (kind) {

                        case AnimationKinds.Once:

                            currentFrame = 0;

                            Heart.Stop();

                            break;

                        case AnimationKinds.OnceToDispose:

                            Heart.Stop();

                            (this.Parent as Canvas).Children.Remove(this);

                            break;

                        case AnimationKinds.Loop:

                            currentFrame = 0;

                            break;

                    }

                }

                this.Background = new ImageBrush() { ImageSource = Global.GetWebImage(string.Format(@"Animation/{0}/{1}{2}", code, currentFrame, format)) };

                currentFrame++;

     };

最后一个问题是我们应该如何处理下载得到的资源才最合理呢?一方面尽量少的占用额外的内存资源;另一方面在下次再请求获取时不用重复执行下载而浪费带宽流量。

针对此问题我的解决思路是:通过静态字典(Dictionary<string, XElement>)缓存xml配置文件,通过延迟加载(BitmapCreateOptions.DelayCreation)与浏览器共用图象缓存,音乐文件则通过MediaElement直接加载,此过程会对加载的视频或音频流进行时时播放,用户体验很好而无须我们任何代码干扰。

到此,从资源配置布局到所有资源按需队列下载并实现缓存,独立于对象的配置布局体系就构建完成了。本文仅以实现自定义动画为例,实际游戏开发中无论精灵、魔法还是场景等均完全可以照般此布局体系。我们的最终目标是让SilverlightXAP包中不存放除代码外任何的额外资源,而将所有需要的部件布局于Web中,真正实现对一切对象的动态按需下载:

看过本文后,是否还会有朋友再询问如何对xap进行分包?如何对xap进行压缩?如何动态下载xaml?没有必要了吧。本文构建的游戏架构体系实现了代码与素材的完全分离,它将使得就算是一款Silverlight网游巨作其XAP也难超500K,客户端更新后用户就算重新下载也仅仅是数秒内的事,这才是Silverlight开发Web网游的强势所在 “动态资源”。

在未来商家必争的手机游戏(手机网游)开发领域,动态资源配置或许将成为刻不容缓需要面对的技术问题,根据场景按需加载资源不仅节约带宽流量且能更快速的进入游戏体现着良好的用户体验。希望本文的解决方案能为大家指引一个正确的方向,识时务者为俊杰,让我们携手努力吧,Silverlight网游的蓝天在等待着您去开创!

本节Demo在线演示地址:http://cangod.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298143.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python pp模块_Python模块--Pexpect

探索 Pexpect&#xff0c;第 1 部分&#xff1a;剖析 Pexpect概述Pexpect 是 Don Libes 的 Expect 语言的一个 Python 实现&#xff0c;是一个用来启动子程序&#xff0c;并使用正则表达式对程序输出做出特定响应&#xff0c;以此实现与其自动交互的 Python 模块。 Pexpect 的使…

mysql root密码忘记2018_2018-03-28设置及修改mysql用户密码学习笔记

退出mysql方法quit或者exit设置及修改mysqlroot用户密码安装mysql后&#xff0c;默认管理员root密码为空&#xff0c;这很不安全&#xff0c;需要设置一个密码&#xff0c;在安装mysql单实例后&#xff0c;有个初始优化的一些安全措施&#xff1a;为root设置了密码删除无用的my…

如何机智的弄坏一台电脑?

全世界只有3.14 % 的人关注了爆炸吧知识原文&#xff1a;http://litten.me/2015/07/06/hack-in-localstorage/作者&#xff1a;Litten很多人都在说&#xff1a;“技术领域当中&#xff0c;前端最没有技术含量&#xff0c;且容易被替代。”有人说&#xff0c;前端的能力界限顶多…

局域网屏幕监控软件

选择一款好的局域网屏幕监控软件要注意哪些问题&#xff1f;相信这是很多企业老板、网管都关心的问题之一。网上各种监控软件那么多&#xff0c;万一下载一个不好用的软件&#xff0c;不仅浪费的是时间&#xff0c;甚至因为软件稳定性不好&#xff0c;影响员工工作效率就不好了…

android 学习查阅笔记

给自己一个链接地址方便找http://www.cnblogs.com/TerryBlog/archive/2010/06/25/1764821.htmlAndroid与IIS身份验证——基本验证http://www.cnblogs.com/GoodHelper/archive/2011/08/17/android_iis_01.html转载于:https://www.cnblogs.com/yjrl/archive/2011/08/14/2137692.h…

pythonlive2d_Unity-Live2D资源载入

在Live2d上下载的资源模型是.cmox格式的&#xff0c;无法直接在unity内使用&#xff0c;需要在Cubism中导出成.moc文件。1.将下载的.cmox资源直接拖入Cubism2.导出的时候有2.0版本和3.0版本的&#xff0c;如图随便哪个版本都OK&#xff0c;这里2.0和3.0的区别是&#xff0c;3.0…

C#为什么会这么慢之命运之终章-真理篇for firelong

firelong我真得希望你来看一看&#xff0c;可惜上一篇你错过了。c#会这么慢的话题却是始终充满着火药味&#xff0c;也许说真话真的很不动听&#xff0c; 可能上次的帖子firelong没看到&#xff0c;讨论C#哪能错过微软&#xff0c;.NET战略是微软当年的号称终极兵器的究级必杀技…

mysql 备份 master_如何配置MYSQL的MASTER---SLAVE复制备份?

一、配置一个mysql服务器做master&#xff1a;在配置文件my.ini中添加如下内容&#xff1a;log-binmatster-binlog-bin-indexmatster-bin.indexserver-id1master必须有一个活动的二进制日志和唯一的server-id,然后重启mysql服务器。其次&#xff0c;以root登录&#xff0c;创建…

豆瓣评分9分+,这6部经典趣味数学纪录片堪称神作!

全世界只有3.14 % 的人关注了爆炸吧知识数学是研究数量、结构、变化以及空间模型等概念的一门学科。透过抽象化和逻辑推理的使用&#xff0c;由计数、计算、量度和对物体形状及运动的观察中产生。数学家们拓展这些概念&#xff0c;为了公式化新的猜想以及从合适选定的公理及定义…

C++游戏开发需要阅读的书籍

如果要自学游戏程序开发的话&#xff0c;可以看看下面的&#xff0c;呵呵。 游戏开发资料&#xff08;PDF书都是中文版的&#xff0c;非英文&#xff0c;很多是本人自己扫描制作&#xff0c;从未网上发布过&#xff0c;所以独家啦&#xff09;&#xff1a; 1、Gamebryo 2.2游…

32位hex转浮点 python_python——int()、hex()、oct()、bin()、float()数值类型转换函数

摘要&#xff1a;在python中&#xff0c;数值类型转换函数常用的有浮点型float()、取整int()、八进制oct()、二进制bin()、十六进制hex()这五个函数。单词float的意思就是浮动的意思&#xff1b;int是单词integer整数的前三个字母&#xff1b;oct是单词八进制octal的前三个字母…

C语言运算符优先级 详细列表

优先级 运算符 名称或含义 使用形式 结合方向 说明 1 [] 数组下标 数组名[常量表达式] 左到右 () 圆括号 &#xff08;表达式&#xff09;/函数名(形参表) . 成员选择&#xff08;对象&#xff09; 对象.成员名 -> 成员选择&#xff08;指针&#xff09; …

我的闪存

快速记录平时需要保存的知识点。博客园的闪存&#xff0c;能输入的内容不丰富&#xff0c;比如不能贴代码。

随机加解密java_JAVA随机数生成 Math.random和java.util.Random使用简介

一、Math.random1Math.random内部使用java.util.Random实现2 直接调用Math.random是产生一个[0&#xff0c;1)之间的随机数public static void testMathRandom() {System.out.println(Math.random());System.out.println(Math.random());}输出&#xff1a;0.97584820103710910.…

找对象不能只看TA的外表

1 别人家的小奶猫简直太萌了&#xff01;2 你为什么抱它不抱我-我不高兴&#xff01;3 这设计怎么讲4 垃圾桶是新的穿越道具&#xff01;5 找对象不能只看TA的外表6 一下都不知道要干嘛了7 在评论区大声告诉我你点的每个赞&#xff0c;我都认真当成了喜欢

locate: database too small: /var/db/locate.databas

问题&#xff1a;在freebsd下运行出现错误&#xff1a; locate: database too small: /var/db/locate.databas 解决&#xff1a;#/usr/libexec/locate.updatedb转载于:https://blog.51cto.com/ahwind/341486

一文说通异步 LINQ

用不好异步 LINQ&#xff0c;基本上就等于用不好 LINQ 了。LINQ 这个东西&#xff0c;出来很早了&#xff0c;写过几年代码的兄弟们&#xff0c;或多或少都用过一些。早期的 LINQ&#xff0c;主要是同步的&#xff0c;直到 C# 8.0 加入 IAsyncEnumerable&#xff0c;LINQ 才真正…

HDU_1541 Stars(树状数组)

poj上1A&#xff0c; HDU上6A&#xff0c;我晕啊&#xff01;注意几点&#xff1a; 1、多组数据&#xff1b; 2、memset(c, 0, siezeof(c)); 3、memset(ans, 0, sizeof(ans)); my code: View Code #include <stdio.h>#include <string.h>#define N 32010int c[N],…

点击ride界面edit空白_『技术锦囊』如何在SOLIDWORKS界面调用宏程序?

SOLIDWORKS宏程序为广大设计开发者提供了非常便捷的开发环境&#xff0c;合理的使用宏程序除了可以节约时间还可以减少很多不必要的操作&#xff0c;例如一键替换图纸等。此次便与大家讲讲&#xff0c;如何在SOLIDWORKS界面调用宏程序。操作流程1、任意开启一张工程图图纸&…

摇滚java游戏_java 集合类

一、Array &#xff0c; ArraysJava所有“存储及随机访问一连串对象”的做法&#xff0c;array是最有效率的一种。1、效率高&#xff0c;但容量固定且无法动态改变。array还有一个缺点是&#xff0c;无法判断其中实际存有多少元素&#xff0c;length只是告诉我们array的容量。2…