跳轉到內容

使用 XNA 建立遊戲/程式設計/框架

來自 Wikibooks,開放世界中的開放書籍

框架的數量和失敗的遊戲開發者一樣多。每次有人無法完成他們的遊戲,或者遊戲結果證明是失敗的時候,開發者就會將剩餘的原始碼變成一個“框架”。幸運的是,有一些實際上有用的框架,在這章中,我們想向您展示一些可以輕鬆快速地用於創建出色遊戲的框架。您應該擔心的一件事是框架釋出的許可證。

LTrees 允許您建立隨機生成的樹木,包括樹幹、樹枝和樹葉。它還具有樹木的迎風動畫。有幾種不同的樹木可用,例如樺樹、松樹和柳樹。您可以在右側看到一個示例。

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();
			
	    //..
        }

現在編譯並啟動您的專案,享受在風中搖曳的樹木吧!

Nuclex 框架

[編輯 | 編輯原始碼]
Nuclex 框架的組裝和層
來源: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 框架的 VectorFont

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.UserInterface [3]

[編輯 | 編輯原始碼]

Nuclex 的這一部分是一個庫,它提供了所有用於遊戲或應用程式的互動式視覺介面的工具。無論如何,圖形物件都可以透過縮放或定位進行調整,它們易於控制(例如狀態更改),並且渲染系統沒有連線。因此,不會對遊戲造成干擾。

為什麼要使用 UserInterface?

[編輯 | 編輯原始碼]
  • 直觀且簡單的設計
  • 跨平臺工作(Xbox 360 和 Windows)
  • 特殊的控制檯 UI 控制元件
  • 支援不同的鍵盤佈局
  • 統一縮放
  • 與渲染器無關的設計
  • 在預設渲染器中的蒙皮(使用 XML 檔案蒙皮元素)
  • 完全測試覆蓋率
使用 Nuclex 框架的簡單視窗

用於在遊戲中快速輕鬆地建立 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;

遊戲狀態管理 [4]

[編輯 | 編輯原始碼]

顧名思義,它是一個協調不同狀態的管理器。一次只能啟用一個狀態,但可以將一個狀態放在另一個狀態之上。例如,主選單可以將正在進行的遊戲放在旁邊,並在退出主選單後返回。

管理器的介面

//  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

參考文獻

[編輯 | 編輯原始碼]
  1. LTrees Demo Application, Change Set 22316. ltrees.codeplex.com,於 2011 年 5 月 28 日獲取
  2. http://nuclexframework.codeplex.com/
  3. http://nuclexframework.codeplex.com/wikipage?title=Nuclex.UserInterface&referringTitle=Home
  4. http://nuclexframework.codeplex.com/wikipage?title=Game%20State%20Management&referringTitle=Home
華夏公益教科書