Silverlight.XNA(C#)跨平台3D游戲研發手記:(九)3D 骨骼動畫


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的游戲開發博客。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM