Silverlight 5和Windows Phone 7.1都已具备SL.XNA模式,这意味着我们可以在相关平台上制作高性能的3D游戏及软件产品而无需二次编码。本节,我将借助一些工具为大家讲解SL.XNA的3D实现原理,并演示如何加载并解析一个功能齐全带贴图和骨骼动画的角色模型。从今天开始,通向3D之大门正全方位为您开启!
关于传统3D游戏的原理并不是本文重点,不再赘述。我们更迫切的需要了解XNA对哪些3D格式支持以便我们可以快速的开始配置开发环境。默认的,XNA开发游戏最常用到.X和.FBX;至于其他的3D文件格式呢?比如Obj、3ds、Md2等等。其实说到底,这与2D游戏中对精灵帧图的解析原理一样,无论什么类型的3D格式,其本质不过就一树形结构文本而已,只是内容较多且相对复杂些罢了;通过之前的教程学习,相信大家都已掌握了如何解析自定义的xml文件,那么通过代码或事先编写好的工具对各类3D文件格式进行解析相信亦并非难事,然后再将之与XNA的3D API对接,从而最终达到展示模型及运行骨骼动画等功能。不难看出,XNA游戏的核心也是最关键环节便是对资源的承载与解析,我们通常称之为内容管道 (ContentPipline),该管道提供了相应接口可随意扩展,从而达到高度自由且全方位覆盖的目的。
3D比起2D来说水深得多,因此为了效率同时也为了降低入门成本,我们完全可以通过一些网上现有资源或开源项目来获取编写好的3D模型内容管道,在此和大家分享我的经验:
1)Skinning Sample – 官方提供的XNA入门级骨骼动画演示Demo(实用度★)
这是微软官方为初学者提供的XNA解析.FBX格式骨骼动画之经典案例,从此,Dude这个名字变得家喻户晓。该源码的核心部分是以下两个类库:
然而实际情况并不乐观:我曾用它测试不下百个FBX带骨骼动画的模型,能够正确解析并正常显示的寥寥无几,尤其对骨骼数支持方面问题尤为严重。提示大家,仅作为示例学习学习便可,除非你有能力对该内容管道进行二次拓展,否则实用性极低。
2)KiloWatt Animation (实用度★★)
这是一款开源的3D骨骼动画解析示例,支持XNA4.0,但目前版本不支持Windows Phone,同时亦测试过十多款.X骨骼动画模型,支持率不高。
3)Animation Component (实用度★★★)
一位韩国3D游戏大师开发的XNA骨骼动画解析开源组件,功能还蛮全的,而且也附带了比较详细的英文教程,暂时还不支持XNA4.0和Windows Phone。
4) XNAnimation (实用度★★★)
巴西人制作的开源的高性能3D骨骼动画支持演示,据作者说将发布XNA4.0版本,可以保持关注。
5)3D FPS Source (实用度★★★)
很难得的比较完整的XNA 3D射击游戏源码,包含的知识点元素很多,只可惜同样不支持XNA4.0和Windows Phone。
6) Axiom (实用度★★★★)
作者介绍如下:
Axiom Engine is an Open-source, cross-platform 3D rendering engine for .NET and Mono licensed using the LGPL. The engine is a high-performance C# port of the powerful OGRE engine and provides full support for DirectX, OpenGL and XNA on Windows, Linux, Android, iPhone and Windows Phone.
说实话,如果真的有作者所述之强大,其前途无可掂量;但至少来说,我暂时还未完全实验成功…
7) XNA Community (实用度★★★★)
超多的XNA各平台游戏源码分享,称其为XNA入门级开发者的福音绝不为过。比如运行于WP7平台上的劳拉RPG Demo,该源码对极复杂(各种资源混合压缩)的MD3(雷神之锤3)格式的骨骼动画解析近乎完美,运行效果非常流畅:
8)Mono (实用度★★★★★)
不用多做介绍了吧,搞.NET若不知道真可以撞墙了。Write Once Play Everywhere是MONO的终极目标,也是XNA要实现全方位跨平台的主流方法。然而,Mono却又并非微软官方所支持的解决方案,这确实是个令人纠结的技术难题。
9)Engine Nine (实用度★★★★★)
一款跨微软所有游戏平台(Windows/Xbox 360/Windows Phone 7/Silverlight)的完全开源3D项目源码(若在商业项目中用到它,请保留Engine Nine的标志,或者…这个你懂的),包含的游戏知识面比较很广,总的来说至少可以搭建一套完整的XNA 3D RPG游戏。
综合各种对比分析,并经过大量的反复测试,最终还是觉得Engine Nine来得给力。尤其是其拓展的素材管道Nine.Content.Pipeline.dll,对Kw X-port导出的.X骨骼动画的支持效果极为出色。下面,我将就如何使用该引擎制作一款SL.XNA模式下的3D模型骨骼动画Demo详细讲解。
(一)导出骨骼动画模型.X文件
从2010版本开始,3D MAX便默认集成了对.FBX格式的导出功能;然而,若想获到.X格式,我们还是得借助比如Panda Directx Exporter或Kw X-port等插件才能实现。
安装好相应插件后我们重启3D MAX,并打开事先准备好的带骨骼动画的角色模型:
这是一款国产MMORPG中非常标准的带全套动作的女侠模型,很适合作为本节Demo的主角。在导出该模型之前,我们需要特别注意此场景中所包含的全部对象并非只有女侠一个,还包括其手中握的剑;若我们直接点击导出,此时3D MAX会将场景中的所有对象一并导出,而这样得到的.X文件解析起来难度大且没什么意义,毕竟我们得考虑到游戏设计中的换装问题。因此,我们只需选中其中的人物部分,然后点击3D MAX的“导出”->“导出选定对象”即可:
至于应该选择何种文件类型,针对Engine Nine来说,经反复测试后发现Kw X-port和Panda DirectX互补导出.X文件格式解析效果很好,下面以 Kw X-port 为例:
最后也是最关键的环节 - 设置导出参数:
常用的导出配置如上图所示,其中我们可以通过右上角的Animation窗口,对该模型的骨骼动画各关键帧进行截取封装并重新命名。比如角色走路动作动画“Walk”,在3D MAX中可以通过调整下方的时间轴获悉角色走路动画的帧区间为65-105之间:
因此,对应导出参数便是Start = 65, Len = 40。至于其他动作动画导出也依此类推。导出完毕后我们将得到1个.X文件和若干张贴图:
显而易见,该模型分为两部分贴图:头部和身体;这就意味着该模型在游戏中能实现3部分的换装:单手武器、头像和衣服。是否有种恍然大悟的感觉?没错,若想为游戏设计实现更为复杂的换装系统,比如衣服、裤子、头饰、护腕、手套、护膝,双手武器等等,则在建模的时候就必须和美术沟通清楚游戏角色方面的需求设定。
(二)配置游戏项目整体环境
按照第六节的方法创建一个新的SL.XNA游戏项目,然后在Content项目中将刚才导出的3个文件加载进去(置于Model文件夹下):
是不是觉得这两张贴图文件的文件名不太好记?OK,我们双击Woman.X进入其神秘的内部,搜索一下“NP134_01.BMP”,发现它俩正好都处于文件的最尾部:
嘿嘿,至于如何处理不用我再多说了吧?
文件就位,剩下的便是解析,终于轮到Engine Nine上场了。
我们首先为Content项目添加拓展的素材管道引用,位于Engine Nice/References/x86/Nine.Content.Pipeline.dll。之后,右键点击Woman.X->属性->设置内容处理器为“Model– Engine Nine”:
接下来,在游戏项目中添加对Engine Nine/References/Windows Phone/下的Nine.dll和Nice.Graphics.dll的引用:
至此,我们便完成了整体环境的配置工作。
(三)加载并解析骨骼动画模型
万事俱备,终于可以大施拳脚。
1)加载模型、网格及骨骼的方法代码:
/// <summary>
/// 获取或设置身体模型资产名称
/// </summary>
public string BodyAssetName {
get { return _BodyAssetName; }
set {
_BodyAssetName = value;
//加载模型资产,检索是否匹配拓展的模型处理管道,并尝试添加该模型的骨骼蒙皮动画等信息
body = contentManager.Load<Model>(value);
bodyGeometry = contentManager.Load<Geometry>(string.Format("{0}Geometry", value));
bodySkeleton = new ModelSkeleton(body);
}
}
string _WeaponAssetName;
/// <summary>
/// 获取或设置武器模型资产名称
/// </summary>
public string WeaponAssetName {
get { return _WeaponAssetName; }
set {
_WeaponAssetName = value;
weapon = contentManager.Load<Model>(value);
}
2)动态切换各部位贴图的方法代码(注意Meshes和MeshParts所对应的模型部位):
/// <summary>
/// 获取或设置脸部纹理贴图资产名称
/// </summary>
public string FaceTextureName {
get { return _FaceTextureName; }
set {
_FaceTextureName = value;
(body.Meshes[0].MeshParts[0].Effect as BasicEffect).Texture = contentManager.Load<Texture2D>(value);
}
}
string _BodyTextureName;
/// <summary>
/// 获取或设置身体纹理贴图资产名称
/// </summary>
public string BodyTextureName {
get { return _BodyTextureName; }
set {
_BodyTextureName = value;
(body.Meshes[0].MeshParts[1].Effect as BasicEffect).Texture = contentManager.Load<Texture2D>(value);
}
3)播放单个骨骼动画的方法代码(可通过名称或序号播放相应骨骼动画):
/// 播放独立骨骼动画
/// </summary>
/// <param name="name">动画名称</param>
public void PlayAnimation(string name) {
BoneAnimation animation = new BoneAnimation(bodySkeleton, body.GetAnimation(name)) {
BlendDuration = TimeSpan.FromSeconds(1) //不同骨骼动画切换时的过度连贯平滑性,若为0则无过度
};
animationPlayer[0].Play(animation); //animationPlayer分别对不同动画进行播放,若[n]值相同,则会相互干扰
4)播放组合骨骼动画的方法代码(比如魔兽世界中,角色便跑动,便施法,还便转头;这里面涉及的技巧很多,但Engine Nine很给力):
/// 播放两组(上下半身)融合骨骼动画
/// </summary>
/// <param name="upperBodyAnimationName">上半身行为动画名</param>
/// <param name="lowerBodyAnimationName">下半身行为动画名</param>
public void PlayAnimation(string upperBodyAnimationName, string lowerBodyAnimationName) {
BoneAnimationController upperBody = new BoneAnimationController(body.GetAnimation(upperBodyAnimationName));
BoneAnimationController lowerBody = new BoneAnimationController(body.GetAnimation(lowerBodyAnimationName)) { Speed = 0.9f };
BoneAnimation animation = new BoneAnimation(bodySkeleton) { BlendDuration = TimeSpan.FromSeconds(1) };
animation.Controllers.Add(upperBody);
animation.Controllers.Add(lowerBody);
//根据模型实际情况,分上下半身处理两组骨骼动画的融合
//比如奔跑+暗器,取上半身暗器骨骼动画显示,取下半身奔跑骨骼动画显示
animation.Controllers[upperBody].Disable("root", false);
animation.Controllers[upperBody].Disable("LThigh", true);
animation.Controllers[upperBody].Disable("RThigh", true);
animation.Controllers[lowerBody].Disable("LPelvis", false);
animation.Controllers[lowerBody].Disable("RPelvis", false);
animation.Controllers[lowerBody].Disable("RCollar", true);
animation.KeyController = lowerBody; //以下半身动画为主
animation.IsSychronized = true; //是否同步
animationPlayer[0].Play(animation);
5)绘制真实影子的方法代码(这是最简单的实现方案,但不是最好的):
/// 绘制模型蒙皮动画
/// </summary>
/// <param name="modelBatch"></param>
/// <param name="world"></param>
public void DrawSkinnedModel(GameTimerEventArgs e, ModelBatch modelBatch, Matrix world) {
if (BodyAssetName != string.Empty) {
//绘制身体骨骼
modelBatch.DrawSkinned(body, world, bodySkeleton.GetSkinTransforms(), bodyEffect);
if (ShowShadow) {
//绘制身体影子
modelBatch.DrawSkinned(body, world * shadow * Matrix.CreateTranslation(new Vector3(-17, -17, -18)), bodySkeleton.GetSkinTransforms(), bodyShadow);
}
}
if (WeaponAssetName != string.Empty) {
//绘制武器模型
modelBatch.Draw(weapon, bodySkeleton.GetAbsoluteBoneTransform("Rfinger") * world, weaponEffect); //把武器模型附加在手的位置(注:"Rfinger"为模型中手的名称)
if (ShowShadow) {
//绘制武器影子
modelBatch.Draw(weapon, bodySkeleton.GetAbsoluteBoneTransform("Rfinger") * world * shadow * Matrix.CreateTranslation(new Vector3(-17, -17, -18)), weaponShadow);
}
}
最后还需要注意一点,关于如何将武器匹配到模型骨骼动画手的位置(实现武器切换功能),代码中我们这样写:modelBatch.Draw(weapon, bodySkeleton.GetAbsoluteBoneTransform("Rfinger") * world, weaponEffect);其中“Rfinger”即对应该模型的右手指部件(当然,实际中应该至于手心处,这个需要和美术沟通好):
总体来说,Engine Nine封装了对.X文件相当完美的骨骼动画解析,就连动画之间的平滑过渡都做得精致到位(BoneAnimation的BlendDuration 参数),就目前来说,足以满足绝大多数手游或页游的3D游戏设计需求。
以下是本节Demo源码下载地址(之后章节将以Windows Phone平台为主,暂时不再提供Silverlight移植版本,开发者们可根据需要自行移植,非常简单):
手记小结:目前的Windows Phone平台不支持自定义着色器(电池寿命问题?),这意味着我们只能使用比如BasicEffect和SkinnedEffect等内置的Shader。而基于浏览器的Silverlight则只能在受信任开启显卡支持的条件下使用3D功能(基于客户端操作系统/显卡等环境因素影响考虑)。虽然依旧存在诸多的不完善,但WP8和WIN8的强劲再一次让我满怀信心;好比本节通过3D MAX + Kw X-port + Panda Directx Exporter + Engine Nine + SL.XNA构建的极具实用价值的高性能跨平台3D骨骼动画游戏案例,作为向3D进军的第一声号角,谁都无法阻挡我们勇往直前的脚步!