silverlight游戏设计(一)主程序加载器

 有段时间没有搞webgame了,近期看到园子里有些silverlight方面文章,忍不住手闲也写几篇文章。作为silverlight webgame的开篇文章,先给大家普及一下主程序加载器的概念。

什么是加载器/为什么需要加载器

我们想一想,一个webgame做下来大概要多大?即便图片资源和其他一些诸如脚本等数据包是动态下载的,我所经历过的webgame最基本的少说也要500KB。对于国人来讲,500KB对于一个游戏已经算是很小了,但是对于webgame而言用户每次都要通过浏览器下载它,当然你可以通过类似“隔离存储”的东西来减少不必要的下载,但对于网速一般的童鞋们依然需要一个糟糕的等待体验(当玩家们看到一堆篮球球在页面中间绕圈,他们会认为这是什么呢?)。所以,通常的做法是:1.先让用户下载一个简单的silverlight下载器,这个下载器包含友好的界面以及一个下载模块,下载模块会去下载所有sl运行的必要文件,在这个过程中像用户提供友好、有趣的UI来减少等待下载的尴尬。2.尽量减少xap的尺寸。这一方面要从很多处做起,养成好习惯。比如需要在开发过程中尽量不要使用额外的程序集(比如解析xml不要用linq,不要使用庞大的sl toolkit,最好一切自己去做自定义控件等);对于一些资源比如图片要打包好,在运行起来时动态下载;合理使用浏览器缓存和隔离存储等等。

好了,我们来看看市面上的webgame加载器都是怎么个效果。

菜友们熟悉的qq农场加载界面:

image

这是

MT大冒险的加载界面:(如果您没听说过请不要惊讶。。这个是我们以前用sl做的)

image

可以看到,其实加载器并没有什么复杂的功能,无非就是只要能把所需的文件下载下来,在UI上给用户一个直观、漂亮的界面就ok(个人认为一个指示进度的UI是非常有用的,当然进度的显示数值该作弊的时候也要作弊~~)。

silverlight中assembly的加载方式及api

.net有两个很重要的概念,一个是assembly(程序集),一个是appdomain(应用程序域),对于silverlight还有一个概念是domain(域)需要注意。assembly(程序集)是一个或多个托管模块,以及一些资源文件的逻辑组合,其实我们可以在其中存放任意格式的资源;appdoman就涉及到.net应用程序体系结构了,appdomain用的最多的还是插件机制的实现,具体您可以参考MSDN。而对于sl中的domain,相信做过sl项目的人都应该了解,假设我们需要sl能够跨域访问服务器,如果是web server的话最省事,只需要放置一个策略文件,如果不是web server的话也没关系,在943端口上监听对策略文件的请求即可并将xml数据传过去即可。还比如sl运行时处于安全考虑,不允许来自两个域的sl程序之间直接进行通信,我们需要通过LocalCommunication来解决sl的跨域通信问题。

好了,上面算是做了个知识普及,其实 silvelright中加载程序集是非常之easy的。我们的程序集当然是从网络读出来的(当然也可能是从IsolatedStorage中读出来),当得到流之后只需调用 Assembly AssemblyPart.Load(Stream stream)即可获得Assembly对象。将所有的程序集载入后就可以通过反射启动我们真正的“程序”了。诸如:

var page = asm.CreateInstance("DemoApp.MainPage") as UserControl;
page.Show()

设计你自己的加载器

我们的加载器要有两个过程,一是把需要的程序集文件下载过来以Assembly对象的形式保存,二是按需加载,通过反射得到我们需要的资源。加载器对外提供必要的接口便于UI使用。

AssemblyLoadManifest:这个类其实存放是需要下载的目标程序集的uri(至于这些uri是什么,你可以将他们放在xml里自己去解析出来)。

image 

 
   
AssemblyLoadManifest
namespace  Sopaco.Silverlight.Loader
{
    
///   <summary>
    
///  需要加载到应用程序域中的清单
    
///  the manifest needed to load int the current appdomain
    
///   </summary>
     public   class  AssemblyLoadManifest
    {
        
#region  Fields
        
///   <summary>
        
///  Uri列表
        
///   </summary>
         private  IList < Uri >  dllUris  =   new  List < Uri > ();
        
///   <summary>
        
///  存储对应的程序集
        
///   </summary>
         private  Dictionary < Uri, Assembly >  _asms  =   new  Dictionary < Uri,Assembly > ();
        
#endregion
 
        
#region  Properties
        
///   <summary>
        
///  Uri
        
///   </summary>
         public  Queue < Uri >  Uris
        {
            
get  
            {
                
return   new  Queue < Uri > (dllUris.ToList());
            }
        }
        
#endregion
 
        
#region  Exposed Public Methods
        
///   <summary>
        
///  增加一个目标程序集的Uri
        
///   </summary>
         public   void  AddUri(Uri uri)
        {
            
if ( ! dllUris.Contains(uri))
            {
                dllUris.Add(uri);
            }
        }
        
///   <summary>
        
///  获得Uri对应的程序集,没有则返回null
        
///   </summary>
         public  Assembly GetAssembly(Uri key)
        {
            
if  ( ! _asms.ContainsKey(key))
                
return   null ;
            
return  _asms[key];
        }
        
///   <summary>
        
///  设置Uri对应的程序集,访问限制:internal
        
///   </summary>
         internal   void  SetAssembly(Uri key, Assembly asm)
        {
            _asms[key] 
=  asm;
        }
        
#endregion
    }
}

 

得到下载清单我们就开始下载。webcilent就足够了。这里注意因为UI上需要显示进度,所以有必要增加几个用于通知下载状态、进度的事件。

image

 
   
LoadAssemblyTask
namespace  Sopaco.Silverlight.Loader
{
    
///   <summary>
    
///  
    
///   </summary>
     public   class  LoadAssemblyTask
    {
        
#region  Fields
        
private   bool  isWorking  =   false ;
        
private   int  oriLength;
        
private  WebClient _webClient;
        
private  Queue < Uri >  _uris;
        
private  AssemblyLoadManifest _manifest;
        
#endregion
 
        
#region  ctors
        
public  LoadAssemblyTask()
        {
 
        }
        
#endregion
 
        
#region  progress status events
        
public   event  Action < Uri,  int >  DownloadPartChanged;
        
public   event  Action < Exception >  DownloadError;
        
public   event  Action DownloadCompleted;
        
public   event  Action LoadCompleted;
        
#endregion
 
        
public   void  BeginTask(AssemblyLoadManifest manifest)
        {
            
if ( ! isWorking)
            {
                _manifest 
=  manifest;
                oriLength 
=   0 ;
                _uris 
=  manifest.Uris;
                
if  (_uris.Count  ==   0 )
                    
return ;
                _webClient 
=   new  WebClient();
                _webClient.OpenReadCompleted 
+=   new  OpenReadCompletedEventHandler(client_OpenReadCompleted);
                _webClient.DownloadProgressChanged 
+=   new  DownloadProgressChangedEventHandler(_webClient_DownloadProgressChanged);
                var nextUri 
=  _uris.Peek();
                DownloadPartChanged.ExecuteSecurity(nextUri, oriLength);
                _webClient.OpenReadAsync(nextUri);
                isWorking 
=   true ;
            }
            
else
            {
                EndTask();
                
if  ( ! isWorking)
                    BeginTask(manifest);
            }
        }
        
public   event  DownloadProgressChangedEventHandler OnPerDownloadProgressChanged;
        
public   void  EndTask()
        {
            
if  (_webClient  !=   null )
            {
                _webClient.CancelAsync();
                _webClient 
=   null ;
                _uris.Clear();
                _uris 
=   null ;
                oriLength 
=   0 ;
                _manifest 
=   null ;
            }
            isWorking 
=   false ;
        }
        
private   void  client_OpenReadCompleted( object  sender, OpenReadCompletedEventArgs e)
        {
            var webClient 
=  sender  as  WebClient;
            
if (e.Error  !=   null   ||  e.Result  ==   null )
            {
                DownloadError.ExecuteSecurity(e.Error);
                webClient.OpenReadCompleted 
-=  client_OpenReadCompleted;
                
return ;
            }
            oriLength 
+=   1 ;
            var part 
=   new  AssemblyPart();
            var asm 
=  part.Load(e.Result);
            _manifest.SetAssembly(_uris.Peek(), asm);
            _uris.Dequeue();
            
if  (_uris.Count  ==   0 )
            {
                DownloadPartChanged.ExecuteSecurity(
null , oriLength);
                DownloadCompleted.ExecuteSecurity();
                LoadCompleted.ExecuteSecurity();
                webClient.OpenReadCompleted 
-=  client_OpenReadCompleted;
                
return ;
            }
            var nextUri 
=  _uris.Peek();
            DownloadPartChanged.ExecuteSecurity(nextUri, oriLength);
            webClient.OpenReadAsync(nextUri);
        }
        
private   void  _webClient_DownloadProgressChanged( object  sender, DownloadProgressChangedEventArgs args)
        {
            
if  (OnPerDownloadProgressChanged  !=   null )
                OnPerDownloadProgressChanged(sender, args);
        }
    }
}

 

最后要测试一下
 
   
loaderTest
private   void  loaderTest()
        {
            
#region  构建一个清单,填充目标程序集对应的Uri
            var manifest 
=   new  AssemblyLoadManifest();
            manifest.AddUri(
new  Uri( " DemoTester.dll " , UriKind.Relative));
            manifest.AddUri(
new  Uri( " Sopaco.Silverlight.Insfrastructure.dll " , UriKind.Relative));
            manifest.AddUri(
new  Uri( " Sopaco.Silverlight.Loader.dll " , UriKind.Relative));
            manifest.AddUri(
new  Uri( " a.dll " , UriKind.Relative));
            
#endregion
            
#region  注册相关事件(每个下载项的进度事件,下载项变更通知,程序集下载完成事件,程序集加载到域中事件)
            var task 
=   new  LoadAssemblyTask();
            task.OnPerDownloadProgressChanged 
+=  (o, e)  =>
            {
                reporter.Text 
=   string .Format( " {0}/{1}:{2} " , e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);
                System.Threading.Thread.Sleep(
100 );
            };
            task.DownloadPartChanged 
+=  (uri, i)  =>
            {
                Storyboard sb 
=   new  Storyboard();
                DoubleAnimation ani 
=   new  DoubleAnimation();
                ani.From 
=  ( double .IsNaN(progressBar.Width))  ?   0  : progressBar.Width;
                ani.To 
=  outterContainer.Width  *  i  /  manifest.Uris.Count;
                ani.Duration 
=   new  Duration(TimeSpan.FromSeconds( 2 ));
                Storyboard.SetTarget(ani, progressBar);
                Storyboard.SetTargetProperty(ani, 
new  PropertyPath( " Width " ));
                sb.Children.Add(ani);
                sb.Begin();
                
// progressBar.Width = progressBarOutter.ActualWidth * i / manifest.Uris.Count;
            };
            
// task.DownloadCompleted += () => MessageBox.Show("download all completed");
            task.LoadCompleted  +=  ()  =>
            {
                
// MessageBox.Show("load all completed");
                System.Reflection.Assembly asm  =  manifest.GetAssembly( new  Uri( " a.dll " , UriKind.Relative));
                var page 
=  asm.CreateInstance( " DemoApp.MainPage " as  UserControl;
            };
            task.DownloadError 
+=  (ex)  =>  MessageBox.Show( " occurs error " );
            
// service.Load();
             #endregion
            
#region  开始下载
            task.BeginTask(manifest);
            
#endregion
            
#region  解除事件
            
//
             #endregion
        }

 

image 

后续

因为对于webgame的主程序加载一般没有特别的需求,所以本篇所写的这个加载器没有涉及一些复杂的功能。有时间会将写一个这个系列的文章,其中会包括一个完整的资源加载管理模块的设计,包括资源的下载、缓存管理、版本判断、动态加载机制等内容,尽请期待。

 

 

 

 

本文代码下载

你可能感兴趣的:(silverlight)