posts - 156,  comments - 5252,  trackbacks - 0

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 ExporterKw 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)加载模型、网格及骨骼的方法代码:

 

加载武器和身体模型、骨骼动画
        string _BodyAssetName;
        /// <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所对应的模型部位):

 

脸部和身体切换贴图
        string _FaceTextureName;
        /// <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>
        
/// 播放独立骨骼动画
        
/// </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>
        
/// 播放两组(上下半身)融合骨骼动画
        
/// </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>
        
/// 绘制模型蒙皮动画
        
/// </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版本

Silverlight版本(在线演示)


手记小结:目前的Windows Phone平台不支持自定义着色器(电池寿命问题?),这意味着我们只能使用比如BasicEffect和SkinnedEffect等内置的Shader。而基于浏览器的Silverlight则只能在受信任开启显卡支持的条件下使用3D功能(基于客户端操作系统/显卡等环境因素影响考虑)。虽然依旧存在诸多的不完善,但WP8和WIN8的强劲再一次让我满怀信心;好比本节通过3D MAX + Kw X-port +  Panda Directx Exporter + Engine Nine + SL.XNA构建的极具实用价值的高性能跨平台3D骨骼动画游戏案例,作为向3D进军的第一声号角,谁都无法阻挡我们勇往直前的脚步!

推荐参考:NowpaperWilliams关于Windows Phone的游戏开发博客。

posted on 2012-05-08 16:03 深蓝色右手 阅读(...) 评论(...) 编辑 收藏