Silverlight MMORPG网页游戏开发课程[一期] 第四课:资源布局之动静结合

引言

标准的MMORPG游戏资源均非常庞大,包括数十甚至上百幅地图,几十种魔法,几百种精灵外加一堆的配置文件和音乐音效等等。Silverlight作为嵌在浏览器中的插件,如能合理的将资源分类处理以布局,不仅能减少最终客户端(XAP)容量,同时也是完美的用户体验;俗话说:动态好,静态快。没错,这就是本节我将向大家讲解的:Silverlight - MMORPG游戏资源布局之动静结合。

4.1游戏资源静态布局(交叉参考:场景编辑器让游戏开发更美好)

游戏中的对象很多,如数据库的记录一样,我们可以按个体区别赋予它们各自一个Code(代号)以标识,同时给以相应的配置文件以描述该对象资源结构。

以精灵为例,3.2中主角用到的精灵素材我们可以将其Code定义为0号,那么全新的资源布局结构整理如下:

Silverlight MMORPG网页游戏开发课程[一期] 第四课:资源布局之动静结合

同时3.2中精灵站立、跑动时的结束帧(EndFrame)等我是以硬编码的形式填写,因此新结构中每个精灵对象的Info.xml配置文件目前必须包含如下信息:

<? xml version = " 1.0 "  encoding = " utf-8 "   ?>
< Sprite
  FullName
= " 双刀 "
  Speed
= " 6 "
  BodyWidth
= " 150 "
  BodyHeight
= " 150 "
  CenterX
= " 75 "
  CenterY
= " 125 "
  StandEndFrame = " 3 "
  StandEffectFrame = " -1 "
  StandInterval = " 300 "
  RunEndFrame = " 5 "
  RunEffectFrame = " -1 "
  RunInterval = " 120 "
  AttackEndFrame = " 4 "
  AttackEffectFrame = " 2 "
  AttackInterval = " 180 "
  CastingEndFrame = " 4 "
  CastingEffectFrame = " 4 "
  CastingInterval = " 150 "
/>

   针对最后的这些Frame帧描述,我们可以在Logic项目中建立一个名为SpriteFrames的帧信息结构体以提高可读性:

代码
namespace  Logic.Struct {
    
public   struct  SpriteFrames {
        
public   int  StandEndFrame {  get set ; }
        
public   int  StandEffectFrame {  get set ; }
        
public   int  StandInterval {  get set ; }
        
public   int  RunEndFrame {  get set ; }
        
public   int  RunEffectFrame {  get set ; }
        
public   int  RunInterval {  get set ; }
        
public   int  AttackEndFrame {  get set ; }
        
public   int  AttackEffectFrame {  get set ; }
        
public   int  AttackInterval {  get set ; }
        
public   int  CastingEndFrame {  get set ; }
        
public   int  CastingEffectFrame {  get set ; }
        
public   int  CastingInterval {  get set ; }
    }
}

    根据以上新增内容,在Sprite精灵类中一一补上相应的属性:

代码
         ///   <summary>
        
///  获取或设置名字
        
///   </summary>
         public   string  FullName {  get set ; }

        
///   <summary>
        
///  获取或设置身体宽
        
///   </summary>
         public   double  BodyWidth {
            
get  {  return   this .Width; }
            
set  {  this .Width  =  value; }
        }

        
///   <summary>
        
///  获取或设置身体高
        
///   </summary>
         public   double  BodyHeight {
            
get  {  return   this .Height; }
            
set  {  this .Height  =  value; }
        }

        
///   <summary>
        
///  获取或设置各动作帧信息
        
///   </summary>
         public  SpriteFrames Frames {  get set ; }

    最后在某精灵的Code被修改时解析对应的Info.xml配置并将值取出赋予相关属性:

         int  _Code;
        
///   <summary>
        
///  获取或设置代号(标识)
        
///   </summary>
         public   int  Code {
            
get  {  return  _Code; }
            
set  {
                _Code 
=  value;
                
// 通过LINQ2XML解析配置文件
                 XElement xSprite  =  Global.LoadXML( string .Format( " Sprite/{0}/Info.xml " , value)).DescendantsAndSelf( " Sprite " ).Single();
                FullName 
=  xSprite.Attribute( " FullName " ).Value;
                Speed 
=  ( double )xSprite.Attribute( " Speed " );
                BodyWidth 
=  ( double )xSprite.Attribute( " BodyWidth " );
                BodyHeight 
=  ( double )xSprite.Attribute( " BodyHeight " );
                Center 
=   new  Point(( double )xSprite.Attribute( " CenterX " ), ( double )xSprite.Attribute( " CenterY " ));
                Frames 
=   new  SpriteFrames() {
                    StandEndFrame 
=  ( int )xSprite.Attribute( " StandEndFrame " ),
                    StandEffectFrame 
=  ( int )xSprite.Attribute( " StandEffectFrame " ),
                    StandInterval 
=  ( int )xSprite.Attribute( " StandInterval " ),
                    RunEndFrame 
=  ( int )xSprite.Attribute( " RunEndFrame " ),
                    RunEffectFrame 
=  ( int )xSprite.Attribute( " RunEffectFrame " ),
                    RunInterval 
=  ( int )xSprite.Attribute( " RunInterval " ),
                    AttackEndFrame 
=  ( int )xSprite.Attribute( " AttackEndFrame " ),
                    AttackEffectFrame 
=  ( int )xSprite.Attribute( " AttackEffectFrame " ),
                    AttackInterval 
=  ( int )xSprite.Attribute( " AttackInterval " ),
                    CastingEndFrame 
=  ( int )xSprite.Attribute( " CastingEndFrame " ),
                    CastingEffectFrame 
=  ( int )xSprite.Attribute( " CastingEffectFrame " ),
                    CastingInterval 
=  ( int )xSprite.Attribute( " CastingInterval " ),
                };
            }
        }

    这里我用到了LINQ TO XMLXAP中的xml文件进行解析;需要注意的是使用时必须在项目中添加对System.XML.LINQ的引用,同时还要using System.Linq程序集。

另外基于性能的考虑,CanvasBackground同样可以填充图片。因而我们可以将精灵中的body类型该为ImageBrush,3.2节为基础修改后的代码如下:

         #region  构造

        
ImageBrush body  =   new  ImageBrush();
        DispatcherTimer dispatcherTimer 
=   new  DispatcherTimer();
        
public  Sprite() {
            
this .Background  =  body;
            
this .Loaded  +=   new  RoutedEventHandler(Sprite_Loaded);
        }

        
private   void  Sprite_Loaded( object  sender, EventArgs e) {
            
Stand();
            dispatcherTimer.Tick 
+=   new  EventHandler(dispatcherTimer_Tick);
            dispatcherTimer.Start();
            
this .Loaded  -=  Sprite_Loaded;
        }

        
int  currentFrame, startFrame, endFrame;
        
void  dispatcherTimer_Tick( object  sender, EventArgs e) {
            
if  (currentFrame  >  endFrame) { currentFrame  =  startFrame; }
            
body.ImageSource  =  Global.GetProjectImage( string .Format( " Sprite/{0}/{1}-{2}-{3}.png " , ID, ( int )State, ( int )Direction, currentFrame));
            currentFrame
++ ;
        }

        
#endregion

    当然,其中我还在Global中添加了两个静态方法分别用于上面的XML文件与Image图像的加载:

         ///   <summary>
        
///  项目Resource资源路径
        
///   </summary>
         public   static   string  ProjectPath( string  uri) {
            
return   string .Format( @" /{0};component/Res/{1} " , ProjectName, uri);
        }

        
///   <summary>
        
///  获取项目Resource中的xml文件
        
///   </summary>
        
///   <param name="uri"> 相对路径 </param>
        
///   <returns> XElement </returns>
         public   static  XElement LoadXML( string  uri) {
            
return  XElement.Load(ProjectPath(uri));
        }

        
///   <summary>
        
///  获取项目Resource中的图片
        
///   </summary>
        
///   <param name="uri"> 相对路径 </param>
        
///   <returns> BitmapImage </returns>
         public   static  BitmapImage GetProjectImage( string  uri) {
            
return   new  BitmapImage( new  Uri(ProjectPath(uri), UriKind.Relative)) {
                CreateOptions 
=  BitmapCreateOptions.None
            };
        }

    到此一个全新的静态资源布局结构(编译后的所有资源均存于主XAP)就完成了。

4.2游戏资源动态布局(交叉参考:创建基于场景编辑器的新游戏Demo  动态资源  三国策(Demo) 之 “江山一统”)

Silverlight中通过WebClinet下载的资源与浏览器共用网页缓存这一特性为我们动态布局游戏资源提供了相当的便利。

所谓动态资源布局即资源文件均存放于服务器网站目录下,根据时时的需求去下载。

4.1的代码为基础,我们首先将Silverlight主项目中的Res文件夹完整的复制到Web项目中的ClientBin目录下,然后删除掉主项目中的Res文件夹下的所有文件,之后新建一个名为Model的文件夹以保存精灵模型。

以精灵的呈现为例,大致思路是当它第一次呈现时,首先以模型的形式出现,此时我们会通过WebClinet队列下载该精灵的配置及图片等资源,一旦全部下载完成时精灵才以真实面目展现:

Silverlight MMORPG网页游戏开发课程[一期] 第四课:资源布局之动静结合

当然这需要一些比较复杂的下载逻辑,首先我们在解决方案中新建一个名为DownloadHelper的类库,并在其内部编写两个类Downloader(下载者)DownloadQueue(下载队列)

代码
namespace  DownloadHelper {

    
///   <summary>
    
///  Web资源下载者
    
///   </summary>
     public   sealed   class  Downloader {

        
///   <summary>
        
///  已下载的资源路径字典
        
///   </summary>
         static  Dictionary < string bool >  res  =   new  Dictionary < string bool > ();

        
///   <summary>
        
///  获取或设置下载对象代号
        
///   </summary>
         public   int  TargetCode {  get set ; }

        
///   <summary>
        
///  资源下载中
        
///   </summary>
         public   event  EventHandler < DownloaderEventArgs >  Loading;

        
///   <summary>
        
///  资源下载完成
        
///   </summary>
         public   event  EventHandler < DownloaderEventArgs >  Completed;

        
string  uri  =   string .Empty;
        
///   <summary>
        
///  通过WebClient下载资源
        
///   </summary>
         public   void  Download( string  uri) {
            
this .uri  =  uri;
            
// 假如该路径资源还未下载过
             if  ( ! res.ContainsKey(uri)) {
                WebClient webClient 
=   new  WebClient();
                webClient.OpenReadCompleted 
+=   new  OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
                webClient.OpenReadAsync(
new  Uri(uri, UriKind.Relative), uri);
                res.Add(uri, 
false );
                
if  (Loading  !=   null ) { Loading( this new  DownloaderEventArgs() { Uri  =  uri }); }
            } 
else  {
                
// 假如该路径资源已下载完成
                 if  (res[uri]) {
                    
if  (Completed  !=   null ) { Completed( this new  DownloaderEventArgs() { Uri  =  uri }); }
                } 
else  {
                    
// 假如该路径资源正在下载,则需要等待,每隔1秒检测一次是否已下载完成
                    DispatcherTimer timer  =   new  DispatcherTimer() { Interval  =  TimeSpan.FromSeconds( 1 ) };
                    timer.Tick 
+=   new  EventHandler(timer_Tick);
                    timer.Start();
                    
if  (Loading  !=   null ) { Loading( this new  DownloaderEventArgs() { Uri  =  uri }); }
                }
            }
        }

        
void  webClient_OpenReadCompleted( object  sender, OpenReadCompletedEventArgs e) {
            
// 该路径资源已下载完成
            WebClient webClient  =  sender  as  WebClient;
            webClient.OpenReadCompleted 
-=  webClient_OpenReadCompleted;
            
string  uri  =  e.UserState.ToString();
            res[uri] 
=   true ;
            
// Completed中捕获stream可实现任意文件类型转化
             if  (Completed  !=   null ) { Completed( this new  DownloaderEventArgs() { Uri  =  uri, Stream  =  e.Result }); }
        }

        
void  timer_Tick( object  sender, EventArgs e) {
            
if  (res[uri]) {
                DispatcherTimer dispatcherTimer 
=  sender  as  DispatcherTimer;
                dispatcherTimer.Stop();
                dispatcherTimer.Tick 
-=  timer_Tick;
                
if  (Completed  !=   null ) { Completed( this new  DownloaderEventArgs() { Uri  =  uri }); }
            }
        }

    }
}

 

代码
namespace  DownloadHelper {

    
///   <summary>
    
///  Web资源下载队列(简单实现)
    
///   </summary>
     public   sealed   class  DownloadQueue {

        
///   <summary>
        
///  已下载的文件路径字典
        
///   </summary>
         static  Dictionary < string bool >  res  =   new  Dictionary < string bool > ();

        
///   <summary>
        
///  获取或设置流程序号
        
///   </summary>
         public   int  Index {  get set ; }

        
///   <summary>
        
///  获取或设置下载对象代号
        
///   </summary>
         public   int  TargetCode {  get set ; }

        
///   <summary>
        
///  等待资源下载完成中
        
///   </summary>
         public   event  EventHandler Waiting;

        
///   <summary>
        
///  资源下载完成
        
///   </summary>
         public   event  EventHandler Completed;

        
string  key;
        
int  uriNum, count;
        List
< string >  uris;
        
///   <summary>
        
///  依据资源地址列表队列下载资源文件
        
///   </summary>
        
///   <param name="uris"> 资源地址列表 </param>
         public   void  Download( string  key, List < string >  uris) {
            
this .key  =  key;
            
// 假如此下载队列未下载过
             if  ( ! res.ContainsKey(key)) {
                
if  (uris.Count  ==   0 ) {
                    
if  (Completed  !=   null ) { Completed( this null ); }
                } 
else  {
                    
this .uris  =  uris;
                    uriNum 
=  uris.Count;
                    Download(count);
                    res.Add(key, 
false );
                }
            } 
else  {
                
// 假如该队列资源已下载完成
                 if  (res[key]) {
                    
if  (Completed  !=   null ) { Completed( this null ); }
                } 
else  {
                    
// 假如该路径资源正在下载,则需要等待,每隔1秒检测一次是否已下载完成
                    DispatcherTimer timer  =   new  DispatcherTimer() { Interval  =  TimeSpan.FromSeconds( 1 ) };
                    timer.Tick 
+=   new  EventHandler(timer_Tick);
                    timer.Start();
                    
if  (Waiting  !=   null ) { Waiting( this null ); }
                }
            }
        }

        
void  Download( int  index) {
            Downloader downloader 
=   new  Downloader();
            downloader.Completed 
+=   new  EventHandler < DownloaderEventArgs > (downloader_Completed);
            downloader.Download(uris[index]);
        }

        
void  downloader_Completed( object  sender, DownloaderEventArgs e) {
            Downloader downloader 
=  sender  as  Downloader;
            downloader.Completed 
-=  downloader_Completed;
            count
++ ;
            
if  (count  <  uriNum) {
                Download(count);
            } 
else  {
                res[key] 
=   true ;
                
if  (Completed  !=   null ) { Completed( this null ); }
            }
        }

        
void  timer_Tick( object  sender, EventArgs e) {
            
if  (res[key]) {
                DispatcherTimer dispatcherTimer 
=  sender  as  DispatcherTimer;
                dispatcherTimer.Stop();
                dispatcherTimer.Tick 
-=  timer_Tick;
                
if  (Completed  !=   null ) { Completed( this null ); }
            }
        }

    }
}

(注:这两个类目前只是简单实现,达到目的为主,后续课程还会进一步做代码优化。大致原理是以静态资源下载状态字典(static Dictionary<string, bool> res)为依据,分三种情况(未下载、下载中、已下载)作出不同的判断处理。)

剩下的工作是在精灵类内添加一个名为Code的属性并封装逻辑:当该值改变时首先通过Downloader去下载该精灵代号的配置文件:

         int  _Code  =   - 1 ;
        
///   <summary>
        
///  获取或设置代号
        
///   </summary>
         public   int  Code {
            
get  {  return  _Code; }
            
set  {
                
if  (_Code  !=  value) {
                    _Code 
=  value;
                    Downloader downloader  =   new  Downloader() { TargetCode  =  value };
                    downloader.Completed  +=   new  EventHandler < DownloaderEventArgs > (downloader_Completed);
                    downloader.Download(Global.WebPath( string .Format( " Sprite/{0}/Info.xml " , value)));
                }
            }
        }

配置文件下载完成后我们接着编写类似4.1的逻辑对该xml文件进行解析,将获取的数据赋值到精灵属性;此时还会得到该精灵模型的代号属性:ModelCode - 用以选择对应的模型首先呈现。最后判断该代号的精灵资源是否下载过,如果没有则创建下载队列去顺次下载所需资源:

代码
         ///   <summary>
        
///  已下载的资源代号
        
///   </summary>
         static  List < int >  loadedCodes  =   new  List < int > ();

        
int  index  =   0 ;
        
int  bodyCode;
        
bool  IsResReady  =   false ;
        
void  downloader_Completed( object  sender, DownloaderEventArgs e) {
            Downloader downloader 
=  sender  as  Downloader;
            downloader.Completed 
-=  downloader_Completed;
            
string  key  =   string .Format( " Sprite{0} " , downloader.TargetCode);
            
if  (e.Stream  !=   null ) { Global.ResInfos.Add(key, XElement.Load(e.Stream)); }
            
// 通过LINQ2XML解析配置文件
            XElement config  =  Global.ResInfos[key].DescendantsAndSelf( " Sprite " ).Single();
            FullName 
=  config.Attribute( " FullName " ).Value;
            Speed 
=  ( double )config.Attribute( " Speed " );
            BodyWidth 
=  ( double )config.Attribute( " BodyWidth " );
            BodyHeight 
=  ( double )config.Attribute( " BodyHeight " );
            Center 
=   new  Point(( double )config.Attribute( " CenterX " ), ( double )config.Attribute( " CenterY " ));
            Frames 
=   new  SpriteFrames() {
                StandEndFrame 
=  ( int )config.Attribute( " StandEndFrame " ),
                StandEffectFrame 
=  ( int )config.Attribute( " StandEffectFrame " ),
                StandInterval 
=  ( int )config.Attribute( " StandInterval " ),
                RunEndFrame 
=  ( int )config.Attribute( " RunEndFrame " ),
                RunEffectFrame 
=  ( int )config.Attribute( " RunEffectFrame " ),
                RunInterval 
=  ( int )config.Attribute( " RunInterval " ),
                AttackEndFrame 
=  ( int )config.Attribute( " AttackEndFrame " ),
                AttackEffectFrame 
=  ( int )config.Attribute( " AttackEffectFrame " ),
                AttackInterval 
=  ( int )config.Attribute( " AttackInterval " ),
                CastingEndFrame 
=  ( int )config.Attribute( " CastingEndFrame " ),
                CastingEffectFrame 
=  ( int )config.Attribute( " CastingEffectFrame " ),
                CastingInterval 
=  ( int )config.Attribute( " CastingInterval " ),
            };
            
ModelCode  =  ( int )config.Attribute( " ModelCode " );
            
if  (State  ==  SpriteState.Stand) { Stand(); }
            Coordinate 
=   new  Point(Coordinate.X  +   0.000001 , Coordinate.Y);
            dispatcherTimer.Start();
            
// 假如没有下载过代号精灵的图片资源则开始下载所需资源
            index ++ ;
            
if  (loadedCodes.Contains(downloader.TargetCode)) {
                bodyCode 
=  Code;
                IsResReady 
=   true ;
            }
else {
                IsResReady 
=   false ;
                
DownloadQueue downloadQueue  =   new  DownloadQueue() { Index  =  index, TargetCode  =  downloader.TargetCode };
                downloadQueue.Completed  +=   new  EventHandler(downloadQueue_Completed);
                
// 解析精灵图片资源地址表
                List < string >  uris  =   new  List < string > ();
                
int  n  =   0 ;
                
for  ( int  i  =   0 ; i  <  ( int )config.Attribute( " StateNum " ); i ++ ) {
                    
switch  (i) {
                        
case   0 : n  =  Frames.StandEndFrame;  break ;
                        
case   1 : n  =  Frames.RunEndFrame;  break ;
                        
case   2 : n  =  Frames.AttackEndFrame;  break ;
                        
case   3 : n  =  Frames.CastingEndFrame;  break ;
                    }
                    
for  ( int  j  =   0 ; j  <  ( int )config.Attribute( " DirectionNum " ); j ++ ) {
                        
for  ( int  k  =   0 ; k  <=  n; k ++ ) {
                            uris.Add(Global.WebPath(
string .Format( " Sprite/{0}/{1}-{2}-{3}.png " , Code, i, j, k)));
                        }
                    }
                }
                
downloadQueue.Download(key, uris);
            }
        }

    由于资源动态下载这整个过程是异步的,因此队列完成后还需增加一个额外的判断:当前下载好的精灵资源是否就是玩家最后一次提交的代号精灵(涉及到资源异步与换装同步问题),一致则bodyCode = Code;并且IsResReady=true

         void  downloadQueue_Completed( object  sender, EventArgs e) {
            DownloadQueue downloadQueue 
=  sender  as  DownloadQueue;
            downloadQueue.Completed 
-=  downloadQueue_Completed;
            
// 由于资源加载是异步,因此呈现时以最新的index资源为准
             if  (downloadQueue.Index  ==  index) { 
                bodyCode 
=  downloadQueue.TargetCode;
                IsResReady 
=   true ;
            }
            loadedCodes.Add(downloadQueue.TargetCode);
        }

最后我们还得修改精灵身体图片切换逻辑以适应动态资源情况(GetWebImage方法在Global中):

         int  currentFrame, startFrame, endFrame;
        
void  dispatcherTimer_Tick( object  sender, EventArgs e) {
            
if  (currentFrame  >  endFrame) { currentFrame  =  startFrame; }
            
body.ImageSource  =
                IsResReady
                
?  Global.GetWebImage( string .Format( " Sprite/{0}/{1}-{2}-{3}.png " , bodyCode, ( int )State, ( int )Direction, currentFrame))
                : Global.GetProjectImage( string .Format( " Model/Sprite/{0}/{1}-{2}-{3}.png " , ModelCode, ( int )State, ( int )Direction, currentFrame));
            currentFrame
++ ;
        }

就这样完成了整个动态资源配置流程。本节以精灵控件为例,按照面向对象的思想将所有资源加载配置逻辑封装在Code属性中,使用起来方便快捷。

4.3游戏资源压缩整合

4.2以理想的方式实现了游戏资源的动态加载,然而每个代号精灵的图片资源有上百张之多,通过WebClinet队列下载效率与体验均不是很好,呈现延迟较大。于是我们想到了以个体精灵对象图片资源为独立单位压缩成zip格式:

Silverlight MMORPG网页游戏开发课程[一期] 第四课:资源布局之动静结合

每次加载指定代号精灵时只需下载一次它的zip资源包即可:  

             // 假如没有下载过代号精灵的图片资源则开始下载所需资源
            index ++ ;
            
if  (bodyImages.ContainsKey(downloader.TargetCode)) {
                IsResReady 
=   true ;
            } 
else  {
                IsResReady 
=   false ;
                Downloader zipDownloader 
=   new  Downloader() { Index  =  index, TargetCode  =  downloader.TargetCode };
                zipDownloader.Completed 
+=   new  EventHandler < DownloaderEventArgs > (zipDownloader_Completed);
                
zipDownloader.Download(Global.WebPath( string .Format( " Sprite/{0}/Body.zip " , Code)));
            }

这里我通过一个静态的资源字典来缓存zip数据流:

         ///   <summary>
        
///  已下载的Zip数据流
        
///   </summary>
        
static  Dictionary < int , StreamResourceInfo >  bodyImages  =   new  Dictionary < int , StreamResourceInfo > ();

每次资源下载完成后同样判断是否为最后切换的角色,并将该资源数据流信息缓存:

         void  zipDownloader_Completed( object  sender, DownloaderEventArgs e) {
            Downloader zipDownloader 
=  sender  as  Downloader;
            zipDownloader.Completed 
-=  zipDownloader_Completed;
            
if  (zipDownloader.Index  ==  index) {
                IsResReady 
=   true ;
            }
             if  ( ! bodyImages.ContainsKey(zipDownloader.TargetCode)) {
                bodyImages.Add(zipDownloader.TargetCode,  new  StreamResourceInfo(e.Stream,  null ));
            }
        }

精灵图片切帧方法则修改如下:

         int  currentFrame, startFrame, endFrame;
        
void  dispatcherTimer_Tick( object  sender, EventArgs e) {
            
if  (currentFrame  >  endFrame) { currentFrame  =  startFrame; }
            
if  (IsResReady) {
                BitmapImage bitmapImage  =   new  BitmapImage();
                bitmapImage.SetSource(Application.GetResourceStream(bodyImages[Code],  new  Uri( string .Format( " {0}-{1}-{2}.png " , ( int )State, ( int )Direction, currentFrame), UriKind.Relative)).Stream);
                body.ImageSource  =  bitmapImage;
            } 
else  {
                 body.ImageSource 
=  Global.GetProjectImage( string .Format( " Model/Sprite/{0}/{1}-{2}-{3}.png " , ModelCode, ( int )State, ( int )Direction, currentFrame));
            }
            currentFrame
++ ;
        }

直观看来,在呈现效果上已经接近完美;然而背后却隐藏着巨大危机:内存会随着不断涌现出的新种类精灵而迅速飚升,同时每次图片呈现时都通过数据流的形式去赋值效率极低。对于少量精灵同时存在的场合此方法还勉强接受,但并不适合大制作中以群为单位的精灵展示。

解决方案1:对具体到每一张图片进行缓存而非StreamResourceInfo,然而内存同样会随着对象类别的增多而持续增长。

解决方案2:类似2.1中精灵使用整图素材可同样实现仅需一次下载的效果,性能折中。

    解决方案3:这也是目前我正在深入研究的终极解决方案,由于最近实在太忙暂时未能实现。大致思路是:精灵等对象的图片资源均封装进各自的XAP中,动态下载指定代号对象的XAP资源包后(将其中的资源合并到主XAP包的资源中?),通过类似加载主程序XAPResource的方式("/...;component/Res/...")去获取该XAP中的路径图片;无论是效率、性能还是用户体验均一条龙完美无暇。期待大家的一同参与,望实现者予赐教(MEF似乎有类似模块?)

本课小结:Silverlight游戏中的资源如果全部打包于主XAP中是非常不友好的用户体验;对肯定会用到的资源以静态模式布局放在主XAP,而其他大部分资源则根据玩家的时时需求去动态下载,这才是最完美的Silverlight MMORPG资源布局解决方案。至于动态部分的资源以何种模式去压缩整合这是未来长期需要优化的模块,仁者见仁智者见智,期待所有Silverlight高手加入到我们游戏开发的行列,为打造最强大的Silverlight - MMORPG游戏引擎而奋斗!

本课源码点击进入目录下载

课后作业

作业说明

参考资料:中游在线[WOWO世界] Silverlight C# 游戏开发:游戏开发技术

教程Demo在线演示地址http://cangod.com

你可能感兴趣的:(silverlight)