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中的動態資源配置。
從Javascript的var到F#的lambda,C#在取之精華,去其糟粕的同時讓自身發展得更為完美,趨勢中彌漫著“動態”給我們編程帶來的無限芳香。“動態”,不論在任何場合都是一種優秀表現;與“動態”相呼應的是“自適應”,從布局的“自適應寬高”到游戲資源的“自適應按需下載”,這些均可以從當下諸多優秀的軟件架構中得到充分體現。
在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直接加載,此過程會對加載的視頻或音頻流進行時時播放,用戶體驗很好而無須我們任何代碼干擾。
到此,從資源配置布局到所有資源按需隊列下載并實現緩存,獨立于對象的配置布局體系就構建完成了。本文僅以實現自定義動畫為例,實際游戲開發中無論精靈、魔法還是場景等均完全可以照般此布局體系。我們的最終目標是讓Silverlight的XAP包中不存放除代碼外任何的額外資源,而將所有需要的部件布局于Web中,真正實現對一切對象的動態按需下載:
看過本文后,是否還會有朋友再詢問如何對xap進行分包?如何對xap進行壓縮?如何動態下載xaml?沒有必要了吧。本文構建的游戲架構體系實現了代碼與素材的完全分離,它將使得就算是一款Silverlight網游巨作其XAP也難超500K,客戶端更新后用戶就算重新下載也僅僅是數秒內的事,這才是Silverlight開發Web網游的強勢所在 – “動態資源”。
在未來商家必爭的手機游戲(手機網游)開發領域,動態資源配置或許將成為刻不容緩需要面對的技術問題,根據場景按需加載資源不僅節約帶寬流量且能更快速的進入游戲體現著良好的用戶體驗。希望本文的解決方案能為大家指引一個正確的方向,識時務者為俊杰,讓我們攜手努力吧,Silverlight網游的藍天在等待著您去開創!
本節Demo在線演示地址:http://cangod.com
總結
以上是生活随笔為你收集整理的Silverlight 2.5D RPG游戏技巧与特效处理:(十六)动态资源的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: mysql 字段 as_mysql 字段
 - 下一篇: 采集企业联系方式的10个经典方法