在我开始写Web传奇的时候,就在想一个问题:如果我把所有的地图,怪物图片,音效等游戏资源都放在XAP包里,这个XAP包就会越来越大。在我很早以前玩传奇2的时候,安装包就300多M,后来传奇3就有1G多了。如果等我的web传奇越写越大的时候,那我的游戏需要loading多久啊,玩家可等不了。所以我把微软的文档找来,发现有独立储存区域这个东西,微软的解释如下:
独立存储数据舱是一个抽象的存储位置,而不是一个具体的存储位置。它由一个或多个独立的存储文件(称为存储区)组成,这些存储文件包含存储数据的实际目录位置。任何类型的数据都可以保存到存储区中。
于是我就有这样的设想:
1.把游戏所需的资源从服务端下载silverlight客户端的独立存储区域里。这样游戏客户端第一次下载完毕后,下次登陆就不用下载这些资源了,可以很快的进入游戏。
2.按每个地图分别做成不同的zip包,如果开新的地图只需要在服务端放上新的地图包,在客户端登陆的时候通知其下载就可以了。
3.xap包里的资源尽量少,除了必要的dll文件,其他都不放,这样我们就能把xap包控制在300K以内,客户端loading的速度就很快了。
根据微软的silverlight3文档,我写了一个独立存储区域资源读写类
/// 独立存储区域操作类 by williams
/// </summary>
internal class IsolatedStorageUtil
{
#region 私有成员
IsolatedStorageFile store ;
internal long availableFreeSpace;
internal long totalSpace;
#endregion
#region 构造函数
internal IsolatedStorageUtil()
{
store = IsolatedStorageFile.GetUserStoreForSite();
availableFreeSpace = store.AvailableFreeSpace;
totalSpace = store.Quota;
}
#endregion
#region 操作方法
/// <summary>
/// 增加新的空间
/// </summary>
/// <param name="spaceSize"></param>
internal bool AddNewSpace(long spaceSize)
{
return store.IncreaseQuotaTo(spaceSize);
}
/// <summary>
/// 得到独立存储区域里的目录
/// </summary>
/// <returns></returns>
internal string[] GetDirectoryNames()
{
if (store != null)
{
return store.GetDirectoryNames();
}
else
{
return new string[] {""};
}
}
/// <summary>
/// 得到独立存储区域里的目录
/// </summary>
/// <param name="searchStr">可以使用?,*匹配符号</param>
/// <returns></returns>
internal string[] GetDirectoryNames(string searchStr)
{
if (store != null)
{
return store.GetDirectoryNames(searchStr);
}
else
{
return new string[] { "" };
}
}
/// <summary>
/// 检查文件夹是否存在
/// </summary>
/// <param name="directName"></param>
/// <returns></returns>
internal bool DirectorExist(string directName)
{
return store.DirectoryExists(directName);
}
/// <summary>
/// 判断文件是否存在
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
internal bool FileExist(string filePath)
{
return store.FileExists(filePath);
}
/// <summary>
/// 获取独立存储区域里的所有文件名
/// </summary>
/// <returns></returns>
internal string[] GetFileNames()
{
if (store != null)
{
return store.GetFileNames();
}
else
{
return new string[] { "" };
}
}
/// <summary>
/// 在独立存储区域里新建目录
/// </summary>
/// <param name="DirectoryName"></param>
/// <returns></returns>
internal bool CreateDirectory(string DirectoryName)
{
if (store != null)
{
store.CreateDirectory(DirectoryName);
return true;
}
else
{
return false;
}
}
/// <summary>
/// 根据制定文件地址创建文件,如果路径不存在,则自动创建目录
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
internal IsolatedStorageFileStream CreateFile(string filePath)
{
if (store != null)
{
filePath = filePath.Replace('\\','/');
//设定文件夹只有两层,比如data/objects.zip
if (filePath.Contains("/"))
{
string diractory = filePath.Substring(0, filePath.IndexOf("/"));// 得到data
string filename = filePath.Substring(filePath.LastIndexOf("/") + 1);//得到filename
if (!DirectorExist(diractory))
{
CreateDirectory(diractory);
}
}
return store.CreateFile(filePath);
}
else
{
return null;
}
}
/// <summary>
/// 把制定内容写入到指定文件中
/// </summary>
/// <param name="filePath"></param>
/// <param name="content"></param>
/// <returns></returns>
internal bool WriteToFile(string filePath,string content)
{
if (store.FileExists(filePath))
{
try
{
using (StreamWriter sw =
new StreamWriter(store.OpenFile(filePath,
FileMode.Open, FileAccess.Write)))
{
sw.WriteLine(content);
return true;
}
}
catch (IsolatedStorageException ex)
{
return false;
}
}
else
{
return false;
}
}
/// <summary>
/// 把制定的流数据写入到指定数据中
/// </summary>
/// <param name="filePath"></param>
/// <param name="fs"></param>
/// <returns></returns>
internal bool WriteToFile(string filePath, Stream fs)
{
if (store.FileExists(filePath))
{
try
{
using (Stream sw =store.OpenFile(filePath,
FileMode.Open, FileAccess.Write))
{
Byte[] data = new byte[fs.Length];
fs.Read(data, 0, (int)fs.Length);
sw.Write(data, 0, (int)fs.Length);
sw.Flush();
sw.Close();
return true;
}
}
catch (IsolatedStorageException ex)
{
return false;
}
}
else
{
return false;
}
}
/// <summary>
/// 从指定文件中读取内容
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
internal string ReadContentFromFile(string filePath)
{
try
{
using (StreamReader reader =
new StreamReader(store.OpenFile(filePath,
FileMode.Open, FileAccess.Read)))
{
string contents = reader.ReadToEnd();
return contents;
}
}
catch (IsolatedStorageException)
{
return "";
}
}
/// <summary>
/// 读取指定文件的流
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
internal Stream ReadStreamFromFile(string filePath)
{
return store.OpenFile(filePath, FileMode.Open, FileAccess.Read);
}
/// <summary>
/// 删除指定的文件
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
internal bool DeleteFile(string filePath)
{
try
{
if (store.FileExists(filePath))
{
store.DeleteFile(filePath);
return true;
}
else
{
return false;
}
}
catch (IsolatedStorageException)
{
return false;
}
}
/// <summary>
/// 删除指定的目录
/// </summary>
/// <param name="dirDelete"></param>
/// <returns></returns>
internal bool DeleteDirectory(string dirDelete )
{
try
{
if (store.DirectoryExists(dirDelete))
{
store.DeleteDirectory(dirDelete);
return true;
}
else
{
return false;
}
}
catch (IsolatedStorageException ex)
{
return false;
}
}
#endregion
}
IsolatedStorageUtil类里的代码很简单,但在后面却非常频繁的使用到。写完了IsolatedStorageUtil类,大家就会问了,那服务端的文件如何如何下载到客户端呢?xap包是自动下载到内存,可是现在我们的资源没有放到xap包里,怎么办?
还是那句话,看微软的官方文档。什么?你说你从来没看过文档,那真可惜了,作为一个.net程序员,“红宝书”般的东西,不说倒背如流,遇到问题的时候翻翻,总能让你茅塞顿开。
我当时在群里大喊:“兄弟们,如果下载文件包到本地啊?”喊了几声,没人回答我。那时候我们群里用silverlight写比较大点的游戏的就5个人,深蓝,我,潮州人,开心银光,上海goods。深蓝写了非常有名的系列文章,潮州人写了SNS社区的游戏,开心银光写了“冒险岛”,上海goods写的就杂了:德州扑克,把深蓝的游戏改成网络版,我呢,就一门心思想把“传奇”搬到web上来。
没人回答我,是因为大家都是才跟随“深蓝”的脚步,踏入silverlight游戏开发的不毛之地,大家都是摸着石头过河,只能靠自己摸索了。
我见没人解答我的问题,想起“红宝书”,忙翻开一看,在“网络和通讯”章节里有一篇叫“按需下载”,这一定是我要的。我现在做的东西不就是“按需下载”么?
原来,从silverlight2后webclient取代了Downloader 地位,专门负责silverlight客户端的网络下载。文档里写的清清楚楚,明明白白,真是“众里寻他千百度,蓦然回首,却在灯火阑珊处。”
大家可以打开MSDN里的文章看看,不是“红宝书”是什么?
看完“红宝书”,我赶忙写出了DownloadHelper类,在这里我有个疑问,我想写一个批量下载多个文件的方法,效果却不是太好,连续下载的文件一多,文件的保存就有问题,有的文件保存失败,如果有谁能改进下这个类里的DownloadFilesByWhile()方法,不盛感激,现在我只用这个方法下载单个文件。
写完后,在测试过程收获还不小,如下:
2.webclient异步下载返回的e.resault 转化成stream可以,不能强行转化为filestream
3.把下载完成后的e.resault转化成stream后作保存操作时,结束的时候流不能关闭,和我们通常的操作不一样,所以这就是为什么我第一个文件能保存,后面不能保存的原因了。我猜测是这个流处于缓冲区里,关闭的话会清空缓冲区,导致后面下载的文件流也被清空。
4.开单独线程下载,和界面线程通讯的话要用线程间通讯的post,或者get方法
5.多文件下载要用循环,但是文件什么时候下载完是不确定的,又是异步的,所以要把下载线程阻止,在一个文件被下载完之前,当文件下载完毕后在解除阻止,进行循环的下一步。