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,一经查实,立即删除!

相关文章

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

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

局域网屏幕监控软件

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

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

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

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

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

找对象不能只看TA的外表

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

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、任意开启一张工程图图纸&…

女人在想什么

1 行吧&#xff0c;这样至少回家不用给洗jiojio了。2 世纪难题——《女人在想什么》3 方法总比困难多4 跟瓜摊大哥学切西瓜5 你是怎样上去的&#xff1f;6 摄影师&#xff1a;我是因为没有对手才做摄影的&#xff01;7 有了女儿后&#xff0c;儿子的处境好像不太妙8 想知道白色…

zoj2271 Chance to Encounter a Girl(DP)

/* 概率计算&#xff1a;按时间为阶段&#xff0c;每个点由上一阶段周围的四个点来维护。 注意事项&#xff1a;1.时间O&#xff08;N^3*T&#xff09;&#xff0c;在问题的边缘时间&#xff0c;所以打表计算。     2.关于概率的求解&#xff0c;如果遇到就结束了&#…

12 个问题搞懂 Redis

都说学习需要带着问题&#xff0c;带着思考进行学习&#xff0c;下面就以问题的形式来学习下 Redis 。1、什么是 Redis &#xff1f;Redis 是一个高性能的 key-value 数据库&#xff1b;作者来自意大利西西里岛的 Salvatore Sanfilippo &#xff1b;Redis 使用 ANSI C 语言编写…

sql企业管理器_Valentina Studio for mac(开源数据库管理器)

Valentina Studio for mac是您使用MySQL&#xff0c;MariaDB&#xff0c;SQL Server&#xff0c;PostgreSQL&#xff0c;SQLite和Valentina DB数据库的通用数据库管理工具。valentina studio mac下载可让您连接所有主要数据库&#xff0c;运行查询并生成图表&#xff0c;以更好…

java程序编六角星_跨平台移动端解决方案—Weex

跨端方案背景一1起因由于客户端Webview内嵌H5的各种受限&#xff0c;例如性能差、JS执行效率低以及伴随着大量的机型兼容问题&#xff0c;于是有了各种混合跨端开发解决方案&#xff1a;Hybrid、React-Native、Weex、Flutter、小程序、快应用等。2优势我们知道各大应用市场对于…

父类可以调用子类的方法吗_python类的继承、多继承及查找方法顺序

讲解类的继承&#xff0c;先用无__init__()方法的类讲解&#xff0c;会更容易理解分三部分讲解1、单继承(无__init__()方法)继承方式&#xff1a;子类(父类):方法重写&#xff1a;防止执行父类的方法。不想继承父类的某个方法&#xff0c;就自己重写一个一样名称的方法执行父类…

解决ASP.NET Core部署到IIS,更新项目另一个程序正在使用此文件,进程无法访问...

问题部署到IIS上的ASP.NET Core项目&#xff0c;在更新的时候会进程占用的错误解决思路初步解决方案&#xff1a;1&#xff0c;关闭应用程序池2&#xff0c;关闭网站3&#xff0c;更新项目缺点&#xff1a;网站没法访问&#xff0c;部署项目停的时间过长答案查询官方文档后&…

下面选项能正确表示JAVA_模拟试题2

Java语言程序设计模拟试题二一、选择题&#xff1a;共20小题&#xff0c;每小题1分&#xff0c;满分20分&#xff1a;请将答案填入题后括号中。1&#xff0e;以下的选项中能正确表示Java语言中的一个整型常量的是 ( )A) 12. B) -20 C) 1,000 D) 4 5 62。下列的变量定义中&#…

使用Redis Stream来做消息队列和在Asp.Net Core中的实现

Redis - Wikipedia写在前面我一直以来使用redis的时候&#xff0c;很多低烈度需求(并发要求不是很高)需要用到消息队列的时候&#xff0c;在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列&#xff0c;而不想引入新的服务&#xff0c;kafka和RabbitMQ等&#xff…

windows挂载ext4_使用 UEFI 双启动 Windows 和 Linux | Linux 中国

这是一份在同一台机器上设置 Linux 和 Windows 双重启动的速成解释&#xff0c;使用统一可扩展固件接口&#xff08;UEFI&#xff09;。来源&#xff1a;https://linux.cn/article-12891-1.html作者&#xff1a;Alan Formy-duval译者&#xff1a;郑&#xff08;本文字数&#x…

oh,我的老伙计,你看看这近五十个dapr视频

oh&#xff0c;我的老伙计&#xff0c;你看看这近五十个 dapr 视频。这不就是你想要的视频资料吗&#xff1f;快来捡走吧&#xff01;开始了&#xff0c;但是没完全开始 Dapr 是一个可移植的、事件驱动的运行时&#xff0c;它使任何开发人员能够轻松构建出弹性的、无状态和有状…

极速理解设计模式系列:2.观察者模式(Observer Pattern)

4个角色&#xff1a;被观察者(Subject/目标对象接口)&#xff0c;具体被观察者(ConcreteSubject/具体目标对象)&#xff0c;观察者(Observer)&#xff0c;具体观察者(ConcreteObserver) 被观察者(Subject/目标对象接口&#xff09;&#xff1a;目标对象的抽象接口 …

16年微软/腾讯云/华为云MVP是怎样炼成的

自由、创新、研究、探索&#xff0c;很难想象到一个IT大神的博客&#xff0c;会将“自由”放在第一位&#xff0c;也许这二字代表的&#xff0c;既是精神&#xff0c;又是情怀。搞微软技术的&#xff0c;大家或多或少都有听说过微软的“最有价值专家”&#xff08;MVP&#xff…