引言
标准的MMORPG游戏资源均非常庞大,包括数十甚至上百幅地图,几十种魔法,几百种精灵外加一堆的配置文件和音乐音效等等。Silverlight作为嵌入在浏览器中的插件,如能合理的将资源分类处理以布局,不仅能减少最终客户端(XAP)容量,同时也是完美的用户体验;俗话说:动态好,静态快。没错,这就是本节我将向大家讲解的:Silverlight - MMORPG游戏资源布局之动静结合。
4.1游戏资源静态布局(交叉参考:场景编辑器让游戏开发更美好)
游戏中的对象很多,如数据库的记录一样,我们可以按个体区别赋予它们各自一个Code(代号)以标识,同时给以相应的配置文件以描述该对象资源结构。
以精灵为例,3.2中主角用到的精灵素材我们可以将其Code定义为0号,那么全新的资源布局结构整理如下:
同时3.2中精灵站立、跑动时的结束帧(EndFrame)等我是以硬编码的形式填写,因此新结构中每个精灵对象的Info.xml配置文件目前必须包含如下信息: 最后在某精灵的Code被修改时解析对应的Info.xml配置并将值取出赋予相关属性: 这里我用到了LINQ TO XML对XAP中的xml文件进行解析;需要注意的是使用时必须在项目中添加对System.XML.LINQ的引用,同时还要using System.Linq程序集。
<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的帧信息结构体以提高可读性:
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>
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; }
/// <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"),
};
}
}
另外基于性能的考虑,Canvas的Background同样可以填充图片。因而我们可以将精灵中的body类型该为ImageBrush,以3.2节为基础修改后的代码如下:
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图像的加载:
/// 项目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队列下载该精灵的配置及图片等资源,一旦全部下载完成时精灵才以真实面目展现:
当然这需要一些比较复杂的下载逻辑,首先我们在解决方案中新建一个名为DownloadHelper的类库,并在其内部编写两个类Downloader(下载者)和DownloadQueue(下载队列):
/// <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