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進軍的第一聲號角,誰都無法阻擋我們勇往直前的腳步!