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农场加载界面:
这是
MT大冒险的加载界面:(如果您没听说过请不要惊讶。。这个是我们以前用sl做的)
可以看到,其实加载器并没有什么复杂的功能,无非就是只要能把所需的文件下载下来,在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里自己去解析出来)。
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上需要显示进度,所以有必要增加几个用于通知下载状态、进度的事件。
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
}
后续
因为对于webgame的主程序加载一般没有特别的需求,所以本篇所写的这个加载器没有涉及一些复杂的功能。有时间会将写一个这个系列的文章,其中会包括一个完整的资源加载管理模块的设计,包括资源的下载、缓存管理、版本判断、动态加载机制等内容,尽请期待。