代码改变世界

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

2010-12-21 09:36  姜 萌@cnblogs  阅读(902)  评论(0编辑  收藏  举报

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

 

 

 

 

本文代码下载