使用 XNA 建立遊戲/程式設計/框架
框架的數量和失敗的遊戲開發者一樣多。每次有人無法完成他們的遊戲,或者遊戲結果證明是失敗的時候,開發者就會將剩餘的原始碼變成一個“框架”。幸運的是,有一些實際上有用的框架,在這章中,我們想向您展示一些可以輕鬆快速地用於創建出色遊戲的框架。您應該擔心的一件事是框架釋出的許可證。
LTrees 允許您建立隨機生成的樹木,包括樹幹、樹枝和樹葉。它還具有樹木的迎風動畫。有幾種不同的樹木可用,例如樺樹、松樹和柳樹。您可以在右側看到一個示例。

將 LTrees 新增到您的專案需要一些工作,但是新增一些具有預定義迎風動畫的簡單樹木的程式碼非常簡短。
首先,您需要 LTrees 原始碼。您可以從 這裡 下載它們。解壓縮檔案,並將專案 “LTreesLibrary” 和 “LTreesPipeline” 新增到您的解決方案中(見下文)。接下來的步驟都在 Visual Studio 中的 *解決方案資源管理器* 中進行。右鍵單擊您的解決方案,然後選擇 *新增 -> 現有專案*。現在,導航到解壓縮的專案,並選擇相應的 *.csproj 檔案以將其新增到您的專案中。您可能需要重新生成您的解決方案(CTRL+Shift+B)。然後,您必須將引用新增到您的主遊戲專案中。右鍵單擊 *引用 -> 新增引用*。選擇 *專案*,然後新增 LTreesLibrary。現在,展開 *內容* 專案,右鍵單擊 *引用* 子選單,並新增 LTreesPipeline 引用(與上面相同的步驟)。LTreesLibrary 的引用現在應該位於專案主資料夾中的 *引用* 部分,而 LTreesPipeline 的引用應該位於專案 *內容* 子資料夾的 *引用* 部分。現在,您需要將樹模型和紋理新增到您的專案中。只需在資源管理器中開啟從下載的原始碼包中解壓縮的 LTreeDemo 專案(此時是普通的 Windows 資源管理器),導航到 *內容* 子資料夾,然後將 *字型*、*紋理* 和 *樹* 資料夾拖放到 Visual Studio 中解決方案資源管理器中游戲專案的 *內容* 資料夾中。
現在我們可以繼續進行相關程式碼。以下示例部分取自原始碼包中提供的 LTrees 演示應用程式 [1]。首先要新增的是 LTrees 庫
using LTreesLibrary.Pipeline;
using LTreesLibrary.Trees;
using LTreesLibrary.Trees.Wind;
我們需要一些全域性變數來載入和建立樹木和動畫。配置檔案變數包含有關不同樹木的資訊。我們還需要一個 TreeLineMesh、一些 SimpleTree 物件、一個 WindStrengthSin(這定義了迎風動畫的模式)和一個 TreeWindAnimator 物件。
public class MyGame : Microsoft.Xna.Framework.Game
{
//...
String profileAssetFormat = "Trees/{0}";
String[] profileNames = new String[]
{
"Birch",
"Pine",
"Gardenwood",
"Graywood",
"Rug",
"Willow",
};
TreeProfile[] profiles;
TreeLineMesh linemesh;
int currentTree = 0;
SimpleTree tree, tree2, tree3;
WindStrengthSin wind;
TreeWindAnimator animator;
//...
}
需要兩個新方法。LoadTreeGenerators() 將有關樹木的資訊載入到內容管理器中,NewTree() 生成一棵簡單的樹木,包括樹幹、樹枝和樹葉。
void LoadTreeGenerators()
{
profiles = new TreeProfile[profileNames.Length];
for (int i = 0; i < profiles.Length; i++)
{
profiles[i] = Content.Load<TreeProfile>(String.Format(profileAssetFormat, profileNames[i]));
}
}
void NewTree()
{
// Generates a new tree using the currently selected tree profile
// We call TreeProfile.GenerateSimpleTree() which does three things for us:
// 1. Generates a tree skeleton
// 2. Creates a mesh for the branches
// 3. Creates a particle cloud (TreeLeafCloud) for the leaves
// The line mesh is just for testing and debugging
//Each tree was loaded into the profiles[]-filed and can be accessed with the numbers 0 to 5. They are chosen randomly here.
Random num = new Random();
tree = profiles[num.Next(0, 5)].GenerateSimpleTree();
tree2 = profiles[num.Next(0, 5)].GenerateSimpleTree();
tree3 = profiles[num.Next(0, 5)].GenerateSimpleTree();
linemesh = new TreeLineMesh(GraphicsDevice, tree.Skeleton);
}
上面的方法在 LoadContent() 方法中呼叫。此外,還會載入迎風動畫。
protected override void LoadContent()
{
// ...
wind = new WindStrengthSin();
animator = new TreeWindAnimator(wind);
LoadTreeGenerators();
NewTree();
// ...
}
最後,需要繪製樹木。這發生在 Draw(GameTime) 方法中。樹木需要適當縮放和平移。此外,我們需要一個 StateBlock 來捕獲並重新應用渲染狀態,因為 LTrees 不會為我們這樣做。如果您省略了它,您很可能會遇到圖形故障。
protected override void Draw(GameTime gameTime)
{
//..
Matrix world = Matrix.Identity;
Matrix scale = Matrix.CreateScale(0.0015f);
Matrix translation = Matrix.CreateTranslation(3.0f, 0.0f, 0.0f);
Matrix translation2 = Matrix.CreateTranslation(-3.0f, 0.0f, 0.0f);
StateBlock sb = new StateBlock(GraphicsDevice);
sb.Capture();
tree.DrawTrunk(world * scale, cam.viewMatrix, cam.projectionMatrix);
tree.DrawLeaves(world * scale, cam.viewMatrix, cam.projectionMatrix);
animator.Animate(tree.Skeleton, tree.AnimationState, gameTime);
sb.Apply();
sb.Capture();
tree2.DrawTrunk(world * scale * translation, cam.viewMatrix, cam.projectionMatrix);
tree2.DrawLeaves(world * scale * translation, cam.viewMatrix, cam.projectionMatrix);
animator.Animate(tree2.Skeleton, tree2.AnimationState, gameTime);
sb.Apply();
sb.Capture();
tree3.DrawTrunk(world * scale * translation2, cam.viewMatrix, cam.projectionMatrix);
tree3.DrawLeaves(world * scale * translation2, cam.viewMatrix, cam.projectionMatrix);
animator.Animate(tree3.Skeleton, tree3.AnimationState, gameTime);
sb.Apply();
//..
}
現在編譯並啟動您的專案,享受在風中搖曳的樹木吧!

來源:nuclexframework.codeplex.com
Nuclex 是一個框架,它實際上包含多個功能。它專門為 XNA 和其他用 .NET 編寫的平臺構建。Nuclex 的優勢在於不同可用模組的獨立性。模組只是指元件,例如 3D 文字渲染或遊戲狀態管理器。它們可以互換,也可以調整。程式設計師可以混合它們,只取其中一些元素。事實上,大多數模組對於遊戲來說都是必不可少的,因此,只使用一個元件可能已經可以幫助縮短完成時間,或者專注於遊戲的其他部分。這些元件是程式設計師 “不重複造輪子” 的有效幫助,並提供了一種可以稍後自定義的解決方案。如果一個遊戲應該包含 GUI、GamePad 輸入、向量字型或其他與遊戲相關的功能 - Nuclex 框架是尋找解決方案的正確地方。
有趣的是,Nuclex 框架是微軟建立的開源社群 www.codeplex.com 的一部分。即使程式碼和元件不是微軟的財產。
所有類和庫都使用完整的測試覆蓋率進行編碼,其中包括對垃圾回收器或記憶體管理的測試。正如該專案所說,所有類都具有 Nuclex 是開源的,因此它可以用於任何型別的專案。使用條款明確說明,這些庫可以在任何遊戲中實現,只要它對其他使用者保持開放。此外,每個遊戲建立者都歡迎加入平臺,與其他 Nuclex 編碼者合作。註冊一個帳戶併成為社群的一部分非常簡單。根據 Nuclex 社群的意見,使用框架元件的唯一要求是對程式語言有充分的理解。除此之外,以下所有元件都可以使程式設計師的生活更愉快。 [2]
Nuclex 框架的功能
- 3D 文字渲染
- 任意原始批處理
- 自動頂點宣告
- 特殊集合
- 文字輸入和標準 PC 遊戲手柄支援
- 核心仿射執行緒池
- 除錯疊加
- 遊戲狀態管理
- LZMA 內容壓縮
- 多執行緒粒子系統
- 矩形填充
- 帶蒙皮的圖形使用者介面
有關每個模組的更多資訊可以在 http://nuclexframework.codeplex.com/ 上找到。由於框架中有很多有用的類,它們的操作可以在網頁上輕鬆跟蹤,因此本文只介紹一些解決方案。在接下來的部分中,將解釋 Nuclex 的三個主要元件。
框架的組裝看起來很複雜,但它實際上只是不同庫的集合,這些庫可以單獨使用。框架的核心包含用於數學、網路和 Windows 窗體的基本類。

Nuclex 框架中最棒的元件之一是向量字型建立。它實際上從 .ttf 檔案中獲取字元,並對每個字元的邊緣進行插值。插值完成後,所有資訊都儲存在一個 .xnb 檔案中,然後可以由 Nuclex.Fonts 庫使用。即使在尺寸很小的情況下,文字看起來也不如大型花哨的標題好,但它是一個很棒的功能。
這些字型可以在 PC 或 Xbox 上無縫使用,甚至比 XNA 中的 SpriteBatch 類更快。
有三種顯示字型的方式。一種是帶輪廓的文字。它基本上從字型中獲取字母,並計算每個字元的邊緣以進行描邊檢視。顯示向量字型的另一種方式是填充方式。技術與之前相同,但它填充了字元。最後,可以使用字元的擠出版本。
首先,重要的是匯入 Nuclex.Fonts 和 Nuclex.Graphics,並提供一個載入 ttf 的 VectorFont 物件。在 LoadContent() 方法中,您現在可以載入字型
using Nuclex.Fonts;
using Nuclex.Graphics;
private VectorFont arialVectorFont;
private Text helloWorldText;
protected override void LoadContent() {
this.arialVectorFont = this.content.Load<VectorFont>("Content/Fonts/Arial");
this.helloWorldText = this.arial24vector.Extrude("Hello IMIs!");
//.....
除了 VectorFont 之外,我們還需要一個類似於 SpriteBatch 的類,名為 TextBatch。使用它的例項,我們實際上可以繪製文字。我們仍然在 LoadContent() 方法中。
///....
this.spriteBatch = new SpriteBatch(this.graphics.GraphicsDevice);
this.textBatch = new TextBatch(this.graphics.GraphicsDevice);
}
private TextBatch textBatch;
最後,我們需要將所有部分連線在一起並繪製文字。當然,選擇不同的填充型別會產生不同的結果。
///....
this.textBatch.ViewProjection = this.camera.View * this.camera.Projection;
this.textBatch.Begin();
this.textBatch.DrawText(
this.helloWorldText, // text mesh to render
textTransform, // transformation matrix (scale + position)
Color.White // text color
);
this.textBatch.End();
Nuclex 的這一部分是一個庫,它提供了所有用於遊戲或應用程式的互動式視覺介面的工具。無論如何,圖形物件都可以透過縮放或定位進行調整,它們易於控制(例如狀態更改),並且渲染系統沒有連線。因此,不會對遊戲造成干擾。
- 直觀且簡單的設計
- 跨平臺工作(Xbox 360 和 Windows)
- 特殊的控制檯 UI 控制元件
- 支援不同的鍵盤佈局
- 統一縮放
- 與渲染器無關的設計
- 在預設渲染器中的蒙皮(使用 XML 檔案蒙皮元素)
- 完全測試覆蓋率

用於在遊戲中快速輕鬆地建立 GUI 的元件。它不是用於複雜設定的 GUI 管理器,而是涵蓋了典型遊戲 GUI 的所有方面。它會根據螢幕自動更改大小,並提供預設檢視/蒙皮,除非選擇自定義規範。
就像在任何其他 GUI 框架中一樣,您可以建立按鈕、視窗以及幾乎所有其他現代介面功能。
在真正開始之前,我們需要一個基本的介面。這個介面需要非常直觀,可以透過 Screen 類來建立。建立一個例項並將其新增到 GuiManager 物件中。GuiManager 負責視窗,因此您需要在前端建立它,可能是在類的建構函式中。
然後,正如之前所述,您可以新增 Screen 物件,併為真正的介面工作做好準備。注意:Viewport 用於將視窗調整到合適的大小。
最後幾行確定了視窗的邊界。如果您省略它們,視窗仍然會顯示,但外觀不會很好。
this.graphics = new GraphicsDeviceManager(this);
this.input = new InputManager(Services, Window.Handle);
this.gui = new GuiManager(Services);
Viewport viewport = GraphicsDevice.Viewport;
Screen mainScreen = new Screen(viewport.Width, viewport.Height);
this.gui.Screen = mainScreen;
mainScreen.Desktop.Bounds = new UniRectangle(
new UniScalar(0.1f, 0.0f), new UniScalar(0.1f, 0.0f), // x and y = 10%
new UniScalar(0.8f, 0.0f), new UniScalar(0.8f, 0.0f) // width and height = 80%
);
現在讓我們從一個普通的按鈕開始。首先,您需要一個 ButtonControl 例項,然後新增文字,最後設定邊界。
ButtonControl newGameButton = new ButtonControl();
newGameButton.Text = "Neu...";
newGameButton.Bounds = new UniRectangle(
new UniScalar(1.0f, -190.0f), new UniScalar(1.0f, -32.0f), 100, 32
}
放置按鈕後,我們可以為其新增一個委託,使其可點選。之後,我們需要將按鈕新增到我們的 mainScreen。這與其他 GUI 管理器非常類似,因為您只需將所有物件新增到不同的元件中。在本例中,我們希望將按鈕放在桌面上(基本上是螢幕的最底層)。
注意:在以下程式碼中,按鈕的委託會開啟一個新視窗。DialogWin 擴充套件了 WindowControl 類,稍後會詳細介紹。
newGameButton.Pressed += delegate(object sender, EventArgs arguments) {
this.gui.Screen.Desktop.Children.Insert(0, new DialogWin());
};
現在我們有了按鈕,並使其可點選,我們可能想要一個新視窗。我們可以透過擴充套件 WindowControl 類並向其新增自己的元件來輕鬆實現這一點。新增意味著我們將它附加到當前視窗,更確切地說是附加到它的“子級”。“子級”是一個由 WindowControl 基類預設例項化的物件。
public partial class DialogWin : WindowControl {
//Initializes a new GUI demonstration dialog
public DialogWin() {
this.nameEntryLabel.Text = "Deine Martikelnummer bitte:";
this.nameEntryLabel.Bounds = new UniRectangle(10.0f, 30.0f, 110.0f, 24.0f);
Children.Add(this.nameEntryLabel);
}
最後,我們想將 GUI 新增到我們的框架中,並使滑鼠可見。使用 Nuclex 框架建立介面就是這麼簡單。
Components.Add(this.gui);
this.gui.DrawOrder = 1000;
IsMouseVisible = true;
顧名思義,它是一個協調不同狀態的管理器。一次只能啟用一個狀態,但可以將一個狀態放在另一個狀態之上。例如,主選單可以將正在進行的遊戲放在旁邊,並在退出主選單後返回。
管理器的介面
// Manages the game states and updates the active game state</summary>
public class GameStateManager {
// Snapshot of the game's timing values
void Update(GameTime gameTime) { /* ... */ }
// Draws the active game state
void Draw(GameTime gameTime) { /* ... */ }
// Pushes the specified state onto the state stack
void Push(GameState state) { /* ... */ }
// Takes the currently active game state from the stack</summary>
void Pop() { /* ... */ }
// This replaces the running game state in the stack with the specified state.
void Switch(GameState state) { /* ... */ }
// The currently active game state. Can be null.</summary>
GameState ActiveState { get { /* ... */ } }
}
Lennart Brüggemann, mglaeser
- ↑ LTrees Demo Application, Change Set 22316. ltrees.codeplex.com,於 2011 年 5 月 28 日獲取
- ↑ http://nuclexframework.codeplex.com/
- ↑ http://nuclexframework.codeplex.com/wikipage?title=Nuclex.UserInterface&referringTitle=Home
- ↑ http://nuclexframework.codeplex.com/wikipage?title=Game%20State%20Management&referringTitle=Home