引言
标准的MMORPG游戏资源均非常庞大,包括数十甚至上百幅地图,几十种魔法,几百种精灵外加一堆的配置文件和音乐音效等等。Silverlight作为嵌在浏览器中的插件,如能合理的将资源分类处理以布局,不仅能减少最终客户端(XAP)容量,同时也是完美的用户体验;俗话说:动态好,静态快。没错,这就是本节我将向大家讲解的:Silverlight - MMORPG游戏资源布局之动静结合。
4.1游戏资源静态布局(交叉参考:场景编辑器让游戏开发更美好)
游戏中的对象很多,如数据库的记录一样,我们可以按个体区别赋予它们各自一个Code(代号)以标识,同时给以相应的配置文件以描述该对象资源结构。
以精灵为例,3.2中主角用到的精灵素材我们可以将其Code定义为0号,那么全新的资源布局结构整理如下:
同时3.2中精灵站立、跑动时的结束帧(EndFrame)等我是以硬编码的形式填写,因此新结构中每个精灵对象的Info.xml配置文件目前必须包含如下信息:
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
<
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的帧信息结构体以提高可读性:
代码
namespace
Logic.Struct {
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>
///
获取或设置名字
///
</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
; }
最后在某精灵的Code被修改时解析对应的Info.xml配置并将值取出赋予相关属性:
int
_Code;
///
<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
"
),
};
}
}
这里我用到了LINQ TO XML对XAP中的xml文件进行解析;需要注意的是使用时必须在项目中添加对System.XML.LINQ的引用,同时还要using System.Linq程序集。
另外基于性能的考虑,Canvas的Background同样可以填充图片。因而我们可以将精灵中的body类型该为ImageBrush,以3.2节为基础修改后的代码如下:
#region
构造
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图像的加载:
///
<summary>
///
项目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(下载队列):
代码
namespace
DownloadHelper {
///
<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
=
uri }); }
}
else
{
//
假如该路径资源已下载完成
if
(res[uri]) {
if
(Completed
!=
null
) { Completed(
this
,
new
DownloaderEventArgs() { Uri
=
uri }); }
}
else
{
//
假如该路径资源正在下载,则需要等待,每隔1秒检测一次是否已下载完成
DispatcherTimer timer
=
new
DispatcherTimer() { Interval
=
TimeSpan.FromSeconds(
1
) };
timer.Tick
+=
new
EventHandler(timer_Tick);
timer.Start();
if
(Loading
!=
null
) { Loading(
this
,
new
DownloaderEventArgs() { Uri
=
uri }); }
}
}
}
void
webClient_OpenReadCompleted(
object
sender, OpenReadCompletedEventArgs e) {
//
该路径资源已下载完成
WebClient webClient
=
sender
as
WebClient;
webClient.OpenReadCompleted
-=
webClient_OpenReadCompleted;
string
uri
=
e.UserState.ToString();
res[uri]
=
true
;
//
Completed中捕获stream可实现任意文件类型转化
if
(Completed
!=
null
) { Completed(
this
,
new
DownloaderEventArgs() { Uri
=
uri, Stream
=
e.Result }); }
}
void
timer_Tick(
object
sender, EventArgs e) {
if
(res[uri]) {
DispatcherTimer dispatcherTimer
=
sender
as
DispatcherTimer;
dispatcherTimer.Stop();
dispatcherTimer.Tick
-=
timer_Tick;
if
(Completed
!=
null
) { Completed(
this
,
new
DownloaderEventArgs() { Uri
=
uri }); }
}
}
}
}
代码
namespace
DownloadHelper {
///
<summary>
///
Web资源下载队列(简单实现)
///
</summary>
public
sealed
class
DownloadQueue {
///
<summary>
///
已下载的文件路径字典
///
</summary>
static
Dictionary
<
string
,
bool
>
res
=
new
Dictionary
<
string
,
bool
>
();
///
<summary>
///
获取或设置流程序号
///
</summary>
public
int
Index {
get
;
set
; }
///
<summary>
///
获取或设置下载对象代号
///
</summary>
public
int
TargetCode {
get
;
set
; }
///
<summary>
///
等待资源下载完成中
///
</summary>
public
event
EventHandler Waiting;
///
<summary>
///
资源下载完成
///
</summary>
public
event
EventHandler Completed;
string
key;
int
uriNum, count;
List
<
string
>
uris;
///
<summary>
///
依据资源地址列表队列下载资源文件
///
</summary>
///
<param name="uris">
资源地址列表
</param>
public
void
Download(
string
key, List
<
string
>
uris) {
this
.key
=
key;
//
假如此下载队列未下载过
if
(
!
res.ContainsKey(key)) {
if
(uris.Count
==
0
) {
if
(Completed
!=
null
) { Completed(
this
,
null
); }
}
else
{
this
.uris
=
uris;
uriNum
=
uris.Count;
Download(count);
res.Add(key,
false
);
}
}
else
{
//
假如该队列资源已下载完成
if
(res[key]) {
if
(Completed
!=
null
) { Completed(
this
,
null
); }
}
else
{
//
假如该路径资源正在下载,则需要等待,每隔1秒检测一次是否已下载完成
DispatcherTimer timer
=
new
DispatcherTimer() { Interval
=
TimeSpan.FromSeconds(
1
) };
timer.Tick
+=
new
EventHandler(timer_Tick);
timer.Start();
if
(Waiting
!=
null
) { Waiting(
this
,
null
); }
}
}
}
void
Download(
int
index) {
Downloader downloader
=
new
Downloader();
downloader.Completed
+=
new
EventHandler
<
DownloaderEventArgs
>
(downloader_Completed);
downloader.Download(uris[index]);
}
void
downloader_Completed(
object
sender, DownloaderEventArgs e) {
Downloader downloader
=
sender
as
Downloader;
downloader.Completed
-=
downloader_Completed;
count
++
;
if
(count
<
uriNum) {
Download(count);
}
else
{
res[key]
=
true
;
if
(Completed
!=
null
) { Completed(
this
,
null
); }
}
}
void
timer_Tick(
object
sender, EventArgs e) {
if
(res[key]) {
DispatcherTimer dispatcherTimer
=
sender
as
DispatcherTimer;
dispatcherTimer.Stop();
dispatcherTimer.Tick
-=
timer_Tick;
if
(Completed
!=
null
) { Completed(
this
,
null
); }
}
}
}
}
(注:这两个类目前只是简单实现,达到目的为主,后续课程还会进一步做代码优化。大致原理是以静态资源下载状态字典(static Dictionary<string, bool> res)为依据,分三种情况(未下载、下载中、已下载)作出不同的判断处理。)
剩下的工作是在精灵类内添加一个名为Code的属性并封装逻辑:当该值改变时首先通过Downloader去下载该精灵代号的配置文件:
int
_Code
=
-
1
;
///
<summary>
///
获取或设置代号
///
</summary>
public
int
Code {
get
{
return
_Code; }
set
{
if
(_Code
!=
value) {
_Code
=
value;
Downloader downloader
=
new
Downloader() { TargetCode
=
value };
downloader.Completed
+=
new
EventHandler
<
DownloaderEventArgs
>
(downloader_Completed);
downloader.Download(Global.WebPath(
string
.Format(
"
Sprite/{0}/Info.xml
"
, value)));
}
}
}
配置文件下载完成后我们接着编写类似4.1的逻辑对该xml文件进行解析,将获取的数据赋值到精灵属性;此时还会得到该精灵模型的代号属性:ModelCode - 用以选择对应的模型首先呈现。最后判断该代号的精灵资源是否下载过,如果没有则创建下载队列去顺次下载所需资源:
代码
///
<summary>
///
已下载的资源代号
///
</summary>
static
List
<
int
>
loadedCodes
=
new
List
<
int
>
();
int
index
=
0
;
int
bodyCode;
bool
IsResReady
=
false
;
void
downloader_Completed(
object
sender, DownloaderEventArgs e) {
Downloader downloader
=
sender
as
Downloader;
downloader.Completed
-=
downloader_Completed;
string
key
=
string
.Format(
"
Sprite{0}
"
, downloader.TargetCode);
if
(e.Stream
!=
null
) { Global.ResInfos.Add(key, XElement.Load(e.Stream)); }
//
通过LINQ2XML解析配置文件
XElement config
=
Global.ResInfos[key].DescendantsAndSelf(
"
Sprite
"
).Single();
FullName
=
config.Attribute(
"
FullName
"
).Value;
Speed
=
(
double
)config.Attribute(
"
Speed
"
);
BodyWidth
=
(
double
)config.Attribute(
"
BodyWidth
"
);
BodyHeight
=
(
double
)config.Attribute(
"
BodyHeight
"
);
Center
=
new
Point((
double
)config.Attribute(
"
CenterX
"
), (
double
)config.Attribute(
"
CenterY
"
));
Frames
=
new
SpriteFrames() {
StandEndFrame
=
(
int
)config.Attribute(
"
StandEndFrame
"
),
StandEffectFrame
=
(
int
)config.Attribute(
"
StandEffectFrame
"
),
StandInterval
=
(
int
)config.Attribute(
"
StandInterval
"
),
RunEndFrame
=
(
int
)config.Attribute(
"
RunEndFrame
"
),
RunEffectFrame
=
(
int
)config.Attribute(
"
RunEffectFrame
"
),
RunInterval
=
(
int
)config.Attribute(
"
RunInterval
"
),
AttackEndFrame
=
(
int
)config.Attribute(
"
AttackEndFrame
"
),
AttackEffectFrame
=
(
int
)config.Attribute(
"
AttackEffectFrame
"
),
AttackInterval
=
(
int
)config.Attribute(
"
AttackInterval
"
),
CastingEndFrame
=
(
int
)config.Attribute(
"
CastingEndFrame
"
),
CastingEffectFrame
=
(
int
)config.Attribute(
"
CastingEffectFrame
"
),
CastingInterval
=
(
int
)config.Attribute(
"
CastingInterval
"
),
};
ModelCode
=
(
int
)config.Attribute(
"
ModelCode
"
);
if
(State
==
SpriteState.Stand) { Stand(); }
Coordinate
=
new
Point(Coordinate.X
+
0.000001
, Coordinate.Y);
dispatcherTimer.Start();
//
假如没有下载过代号精灵的图片资源则开始下载所需资源
index
++
;
if
(loadedCodes.Contains(downloader.TargetCode)) {
bodyCode
=
Code;
IsResReady
=
true
;
}
else
{
IsResReady
=
false
;
DownloadQueue downloadQueue
=
new
DownloadQueue() { Index
=
index, TargetCode
=
downloader.TargetCode };
downloadQueue.Completed
+=
new
EventHandler(downloadQueue_Completed);
//
解析精灵图片资源地址表
List
<
string
>
uris
=
new
List
<
string
>
();
int
n
=
0
;
for
(
int
i
=
0
; i
<
(
int
)config.Attribute(
"
StateNum
"
); i
++
) {
switch
(i) {
case
0
: n
=
Frames.StandEndFrame;
break
;
case
1
: n
=
Frames.RunEndFrame;
break
;
case
2
: n
=
Frames.AttackEndFrame;
break
;
case
3
: n
=
Frames.CastingEndFrame;
break
;
}
for
(
int
j
=
0
; j
<
(
int
)config.Attribute(
"
DirectionNum
"
); j
++
) {
for
(
int
k
=
0
; k
<=
n; k
++
) {
uris.Add(Global.WebPath(
string
.Format(
"
Sprite/{0}/{1}-{2}-{3}.png
"
, Code, i, j, k)));
}
}
}
downloadQueue.Download(key, uris);
}
}
由于资源动态下载这整个过程是异步的,因此队列完成后还需增加一个额外的判断:当前下载好的精灵资源是否就是玩家最后一次提交的代号精灵(涉及到资源异步与换装同步问题),一致则bodyCode = Code;并且IsResReady=true。
void
downloadQueue_Completed(
object
sender, EventArgs e) {
DownloadQueue downloadQueue
=
sender
as
DownloadQueue;
downloadQueue.Completed
-=
downloadQueue_Completed;
//
由于资源加载是异步,因此呈现时以最新的index资源为准
if
(downloadQueue.Index
==
index) {
bodyCode
=
downloadQueue.TargetCode;
IsResReady
=
true
;
}
loadedCodes.Add(downloadQueue.TargetCode);
}
最后我们还得修改精灵身体图片切换逻辑以适应动态资源情况(GetWebImage方法在Global中):
int
currentFrame, startFrame, endFrame;
void
dispatcherTimer_Tick(
object
sender, EventArgs e) {
if
(currentFrame
>
endFrame) { currentFrame
=
startFrame; }
body.ImageSource
=
IsResReady
?
Global.GetWebImage(
string
.Format(
"
Sprite/{0}/{1}-{2}-{3}.png
"
, bodyCode, (
int
)State, (
int
)Direction, currentFrame))
: Global.GetProjectImage(
string
.Format(
"
Model/Sprite/{0}/{1}-{2}-{3}.png
"
, ModelCode, (
int
)State, (
int
)Direction, currentFrame));
currentFrame
++
;
}
就这样完成了整个动态资源配置流程。本节以精灵控件为例,按照面向对象的思想将所有资源加载配置逻辑封装在Code属性中,使用起来方便快捷。
4.3游戏资源压缩整合
4.2以理想的方式实现了游戏资源的动态加载,然而每个代号精灵的图片资源有上百张之多,通过WebClinet队列下载效率与体验均不是很好,呈现延迟较大。于是我们想到了以个体精灵对象图片资源为独立单位压缩成zip格式:
每次加载指定代号精灵时只需下载一次它的zip资源包即可:
//
假如没有下载过代号精灵的图片资源则开始下载所需资源
index
++
;
if
(bodyImages.ContainsKey(downloader.TargetCode)) {
IsResReady
=
true
;
}
else
{
IsResReady
=
false
;
Downloader zipDownloader
=
new
Downloader() { Index
=
index, TargetCode
=
downloader.TargetCode };
zipDownloader.Completed
+=
new
EventHandler
<
DownloaderEventArgs
>
(zipDownloader_Completed);
zipDownloader.Download(Global.WebPath(
string
.Format(
"
Sprite/{0}/Body.zip
"
, Code)));
}
这里我通过一个静态的资源字典来缓存zip数据流:
///
<summary>
///
已下载的Zip数据流
///
</summary>
static
Dictionary
<
int
, StreamResourceInfo
>
bodyImages
=
new
Dictionary
<
int
, StreamResourceInfo
>
();
每次资源下载完成后同样判断是否为最后切换的角色,并将该资源数据流信息缓存:
void
zipDownloader_Completed(
object
sender, DownloaderEventArgs e) {
Downloader zipDownloader
=
sender
as
Downloader;
zipDownloader.Completed
-=
zipDownloader_Completed;
if
(zipDownloader.Index
==
index) {
IsResReady
=
true
;
}
if
(
!
bodyImages.ContainsKey(zipDownloader.TargetCode)) {
bodyImages.Add(zipDownloader.TargetCode,
new
StreamResourceInfo(e.Stream,
null
));
}
}
精灵图片切帧方法则修改如下:
int
currentFrame, startFrame, endFrame;
void
dispatcherTimer_Tick(
object
sender, EventArgs e) {
if
(currentFrame
>
endFrame) { currentFrame
=
startFrame; }
if
(IsResReady) {
BitmapImage bitmapImage
=
new
BitmapImage();
bitmapImage.SetSource(Application.GetResourceStream(bodyImages[Code],
new
Uri(
string
.Format(
"
{0}-{1}-{2}.png
"
, (
int
)State, (
int
)Direction, currentFrame), UriKind.Relative)).Stream);
body.ImageSource
=
bitmapImage;
}
else
{
body.ImageSource
=
Global.GetProjectImage(
string
.Format(
"
Model/Sprite/{0}/{1}-{2}-{3}.png
"
, ModelCode, (
int
)State, (
int
)Direction, currentFrame));
}
currentFrame
++
;
}
直观看来,在呈现效果上已经接近完美;然而背后却隐藏着巨大危机:内存会随着不断涌现出的新种类精灵而迅速飚升,同时每次图片呈现时都通过数据流的形式去赋值效率极低。对于少量精灵同时存在的场合此方法还勉强接受,但并不适合大制作中以群为单位的精灵展示。
解决方案1:对具体到每一张图片进行缓存而非StreamResourceInfo,然而内存同样会随着对象类别的增多而持续增长。
解决方案2:类似2.1中精灵使用整图素材可同样实现仅需一次下载的效果,性能折中。
解决方案3:这也是目前我正在深入研究的终极解决方案,由于最近实在太忙暂时未能实现。大致思路是:精灵等对象的图片资源均封装进各自的XAP中,动态下载指定代号对象的XAP资源包后(将其中的资源合并到主XAP包的资源中?),通过类似加载主程序XAP中Resource的方式("/...;component/Res/...")去获取该XAP中的路径图片;无论是效率、性能还是用户体验均一条龙完美无暇。期待大家的一同参与,望实现者予赐教(MEF似乎有类似模块?)。
本课小结:Silverlight游戏中的资源如果全部打包于主XAP中是非常不友好的用户体验;对肯定会用到的资源以静态模式布局放在主XAP,而其他大部分资源则根据玩家的时时需求去动态下载,这才是最完美的Silverlight MMORPG资源布局解决方案。至于动态部分的资源以何种模式去压缩整合这是未来长期需要优化的模块,仁者见仁智者见智,期待所有Silverlight高手加入到我们游戏开发的行列,为打造最强大的Silverlight - MMORPG游戏引擎而奋斗!
本课源码:点击进入目录下载
课后作业:
作业说明:
参考资料:中游在线[WOWO世界] 之 Silverlight C# 游戏开发:游戏开发技术
教程Demo在线演示地址:http://cangod.com