共用方式為


XNA 實驗室

使用 XNA 框架進行遊戲開發

 

概觀

這次實驗將向你介紹在 Windows Phones 平台上用 XNA Game Studio 進行遊戲開發以及 XNA Game Studio 的基本使用方法。在這次實驗中,你將會建構一個簡單的 XNA Game Studio遊戲應用,進而介紹 XNA Game Studio 遊戲開發的基本概念,同時你將學會如何用 Windows Phone 的 Microsoft Visual 2010 Express 來建置和設計你的 XNA Game Studio。


目標

透過本次實驗,你將會學得:

  • 瞭解 Windows Phone 7 應用裡的 XNA Game Studio 遊戲引擎模型。
  • 學習如何在你的 XNA Game Studio 遊戲中使用資源 (圖片,字體等)。
  • 學習如何添加遊戲邏輯。
  • 學習 Windows Phone XNA Game Studio 遊戲裡的繪圖機制。

前置條件

以下是完成本次動手實驗所必須的條件:

  • 用於 Windows Phone 的 Microsoft Visual Studio 2010 Express 或者 Microsoft Visual Studio 2010
  • Windows Phone 開發工具

**說明:**所有的工具都可以從 http://developer.windowsphone.com 下載


安裝程式碼片段

為了方便起見,本次實驗所使用的大部分程式碼都可以作為 Visual Studio 的程式碼片段:

執行 Source\Setup 資料夾下的 .vsi 安裝程式。

**說明:**如果你在執行程式碼片段的安裝程式時遇到問題,你可以透過拷貝 Source \ Setup \ CodeSnippets 資料夾下的所有 .snippet 檔案到目錄:

\ My Documents \ Visual Studio 2010 \ Code Snippets \ Visual C# \ My Code Snippets來進行手動安裝程式碼片段。


使用程式碼片段

透過程式碼片段,你可以隨時獲取所有你想要的程式碼。本實驗手冊將會準確的告訴你什麼時候使用它們。例如:


圖例 1
使用 Visual Studio 程式碼片段把程式碼插入到你的專案裡

為了把程式碼片段添加到 Visual Studio 中,你只要把游標放在你想插入的程式碼上,開始輸入段落的名稱 (沒有空格和連字型大小),看到智慧感知的段落名,然後當你想要的段落名被選中時,敲擊 Tab 鍵兩次,程式碼將會被插入到游標的位置。


圖例 2
開始輸入段名


圖例 3
敲擊 Tab 鍵來選中反白的段落


圖例 4
再次敲擊 Tab 鍵來展開段落

使用滑鼠而不是鍵盤來插入程式碼片段的方法,在你想插入程式碼片段的地方按右鍵,選擇 My Code Snippets 下面的 Insert Snippet,然後從列表中挑選相關的程式碼片段。

想學習更多 Visual Studio 智慧感知程式碼片段,包括如何建立自己的程式碼片段,請參考 https://msdn.microsoft.com/en-us/library/ms165392.aspx


任務

本次動手實驗包括以下任務:

  1. XNA Game Studio Game 的基本使用。
  2. XNA  框架資源。
  3. XNA Game Studio 遊戲主迴圈。
  4. XNA Game Studio 遊戲輸入。
  5. 外星人遊戲的具體邏輯。

本次動手實驗需要 60分鐘


練習 1:Windows Phone 7 上的 XNA Game Studio 遊戲

如果你曾經想製作你自己的遊戲,Microsoft® XNA™ Game Studio 4.0 就是為你量身定做的。學生,遊戲愛好者,獨立的遊戲開發人員-任何人可以透過 XNA Game Studio 製作並分享偉大的遊戲。

XNA Game Studio 4.0 是一款遊戲開發產品,它基於 Windows Phone 的 Microsoft Visual Studio 2010 Express,為遊戲開發者提供強大而簡單的 C# 程式設計語言。XNA Game Studio 4.0 包括 XNA 框架 (Framework) 和框架內容管道 (Framwork Content Pipeline),從而提供了一種簡單而靈活的方式把 3D 模型,材質,聲音和其他資源導入到你的遊戲中,並且一個專注於遊戲的應用程式設計介面簡化了 Xbox 360®,Windows® 和現在的 Windows Phone 7®上的遊戲開發。

XNA 框架是一個應用程式設計介面,即微軟開發的一個框架來幫助你更快的製作遊戲。但是,它不是一個可以拖曳的遊戲製作框架,所以你需要在使用它之前學習如果程式設計。它非常容易上手,但是你需要有些技術基礎用它進行遊戲開發。

XNA 框架不是一個遊戲引擎。它不包括物理學,碰撞檢測或其他經常可以在遊戲引擎中找到的東西。它是一個遊戲開發框架,但是遊戲如何工作,完全取決於你的程式設計。

在本次實驗中,你將建置一個完整的 Windows Phone 上的 XNA Game Studio 遊戲 —“外星人遊戲 (Alien Game)”— 一款簡單的射擊遊戲。外星人遊戲的目標很簡單:盡可能長地保護地球免受外星人的入侵。你玩的越長,遊戲難度就越大。當心小外星人的夜間偷襲!

總體框架

有些遊戲包括幾個螢幕,每個螢幕代表一個不同的等級,但是這個遊戲只有一個遊戲螢幕。你可以利用稍加修改遊戲邏

外星人遊戲使用的遊戲螢幕管理框架來自於 Game State Management 的樣本 (最初來源於 https://creators.xna.com/en-US/samples/gamestatemanagement),為本次實驗提供了資源。這個遊戲包括 3 個可能的狀態:

  • 主選單 (MainMenuScreen 類別)。
  • 玩遊戲 (GameplayScreen 類別)。
  • 暫停 (PauseScreen 類別)。

外星人遊戲在啟動時會載入所有的內容。第一件要做的事情是載入並顯示背景畫面,然後載入並顯示載入畫面,載入畫面載入內容。螢幕不會自己畫任何東西,所以玩家看到的是包括標題的背景畫面。載入畫面會繪製或顯示某種進度的形式,但是通常載入過程很快,以至於你將看不到進度的顯示。一旦所有的內容載入完畢,主功能表畫面會被載入並顯示,而且功能表項目會繪製到螢幕上。這可以使主功能表和玩遊戲之間快速切換,不需要長時間的暫停來載入內容。如果硬體驅動器降速,這將有助於基於硬體驅動的設備。

GameplayScreen 和 Game 類別

外星人遊戲的設計和實現相當簡單。所有的邏輯和繪製都包含在 GameplayScreen 類別中。在 GameplayScreen.cs 內有一些特定遊戲的類型,被稱為Bullet,Alien 和Player。GameplayScreen.cs 裡的程式碼按功能進行了分類:載入和卸載內容,更新和遊戲模擬 (Game Simulation) 以及繪圖 (Drawing)。

ParticleSystem

外星人遊戲包括一個簡單的 sprite-based 的微系統,用於爆炸和除塵效果。效果的定義和製作是寫死到 ParticleSystem 程式裡的,並且可以被 CreateXXXEffect 的工廠方法 (Factory Method) 存取。

完整的遊戲外觀如下:


圖例 5
Windows Phone 上執行的外星人遊戲

XNA Game Studio 遊戲的基本方法

遊戲是分級制的,等級和遊戲情節,遊戲玩家,敵人等相互關聯。在簡單的情況下,每個等級都可以視為一場完整的遊戲。

一個等級通常有 3 種狀態:

  • 載入 (Load) – 在這種狀態下,系統載入資源,建置與等級相關的變數,計算遊戲世界 (遊戲世界是所有遊戲過程發生的地方),並且執行其他遊戲開始之前必須執行的任務。在等級或遊戲的生命週期裡,這個狀態只發生一次。
  • 更新 (Update) – 在這種狀態下,系統需要更新遊戲世界的狀態。通常意味著計算活動人物(玩家和敵人) 的新位置,更新健康狀況,彈藥和其他狀態,根據遊戲重新計算分數和其他遊戲邏輯。只要遊戲引擎持續執行,這個狀態就一直發生。
  • 繪製 (Draw) –在這種狀態下,系統在圖像輸出設備上,繪製在更新狀態中計算出的變化的地方。只要遊戲引擎持續執行,這個狀態就一直發生。

在 XNA 框架中,最後兩個狀態會在 PC 或 Xbox 360 上以每秒 60 次的速度發生,在 Zune,Zune HD 或 Windows Phone 7 設備上以每秒 30 次的速度發生。


任務 1 – XNA Game Studio 的基本使用

在本部分中,你將建立自己的第一個 Windows Phone 上的 XNA Game Studio 遊戲。這個遊戲很簡單,但是你將在本次操作中添加功能。

**說明:**本次動手實驗的步驟闡明了 Windows Phone 上 Microsoft Visual Studio 2010 Express 的使用過程,但是它們同樣適用於內含 Windows Phone 開發工具的 Microsoft Visual Studio 2010。通常,操作步驟裡提到的 Visual Studio,對這兩個產品都適用。

1. 從開始|所有程式| Microsoft Visual Studio 2010 Express | Microsoft Visual Studio 2010 Express for Windows Phone 裡打開 Microsoft Visual Studio 2010 Express for Windows Phone

Visual Studio 2010:開始|所有程式| Microsoft Visual Studio 2010 裡打開 Visual Studio 2010。

2. 在 File 功能表中,選擇 New Project

**Visual Studio 2010:**在 File 功能表中,點擊 New,然後選擇 Project.

3. 在 New Project 對話方塊中,選擇 XNA Game Studio gamefor Windows Phone 類別,並且在已安裝的範本清單中選擇 Windows Phone Game (4.0)。然後,輸入名稱 AlienGame 和解決方案名稱 Begin。設定專案位置為 Source 資料夾下的 **Ex1-AlienGame。**點擊 OK


圖例 6
在 Microsoft Visual Studio 2010 Express for Windows Phone 裡建立一個新的 Windows Phone Game 應用專案

4. 在 Solution Explorer 裡,檢查由 Windows Phone 應用範本產生的解決方案的結構。任何 Visual Studio 解決方案都是一個包括相關專案的容器。在本案例中,它包括一個名為 AlienGame 的 XNA Game Studio game for Windows Phone 的專案和一個名為 AlienGameContent 的相關遊戲資源專案。


圖例 7
Solution Explorer 展示 AlienGame 的應用

說明: Solution Explorer 允許你在一個解決方案或專案裡查看項目並執行項目管理任務 。按 CTRL + W,S 或在 View 功能表中,選擇 Other Windows | Solution Explorer,可以打開 Solution Explorer。

5. 生成的專案包括一個預設的遊戲實現,這個遊戲實現包括基本的 XNA Game Studio 遊戲迴圈。它位於 Game1.cs 文件裡。

6. 打開 me1.cs 文件。我們建議你將預設的名稱改成一個可以反映你遊戲的名稱。

7. 重新命名主遊戲類別名 (預設的名稱Game1) 為 AlienGame。為了重新命名,在類別名上按右鍵,選擇 Refactor | Rename


圖例 8
重新命名主遊戲類別名

8. 在重新命名對話方塊 New name 的欄位中,輸入 AlienGame 並點擊 OK


圖例 9
為主遊戲類別命名

9. 檢查 Visual Studio 推薦的修改地方並點擊 Apply


圖例 10
對主遊戲類別應用修改

10. 重新命名檔案案名稱與新的類別名對應。在 Solution Explorer 裡的 Game1.cs 上按右鍵並選擇 Rename,把檔案重新命名為 AlienGame.cs


圖例 11
重新命名主遊戲類別檔案案

11. GameThumbnail.png 檔案包括了在電話設備上的快速啟動欄裡識別應用的圖示。你可以在 Solution Explorer 裡按兩下這個條目透過你電腦上註冊的圖片編輯程式來打開檔案,例如 Paint.exe (小畫家)。在本次操作中,你將會更改它。

**說明:**在 Visual Studio 2010 裡,在Solution Explorer 按兩下圖示檔案打開內置的圖片編輯器。

一個 XNA Game Studio game for Windows Phone 的應用典型地利用了基本平台或其他類別庫提供的服務。要使用這些功能,應用需要引用實現這些服務的相應函式組件。

12. 為了顯示專案引用的函式組件,在 Solution Explorer 裡展開 References 節點 並檢查列表。列表包括常規的 XNA 框架函式組件和專門用於 Windows Phone 平台的函式組件。


圖示 12
Solution Explorer 展示專案引用的函式組件

目前,這個應用沒做太多的事,但是為第一次測試執行做好了準備。在這步驟中,你建立應用程式並部署到 Windows Phone 模擬器上,然後執行它來理解典型開發的流程。

13. 在 Debug 功能表中,選擇 Windows | Output 來打開 Output 視窗。

14. 在 Debug 功能表中選擇 Build Solution 或者按下 SHIFT + F6 複合鍵來編譯解決方案裡的專案。

**Visual Studio 2010:**在 Build 功能表中選擇 Build Solution 或者按下 CTRL + SHIFT + B 來編譯解決方案裡的專案。

15. 觀察 Output 視窗並檢查在過程中產出的追蹤資訊包括它輸出結果的最終資訊。


圖例 13
在 Visual Studio 裡建置應用程式

在這個階段,你應該看不到任何錯誤,但是如果專案包含編譯錯誤,這些錯誤將會顯示在 Output 視窗。 你可以利用 Error List 視窗來處理這些錯誤。這個視窗會以清單的形式顯示編譯器產生的錯誤,警告和資訊,你可以根據錯誤的嚴重等級進行排序過濾。此外,你可以按兩下列表裡的條目來自動地打開相關的原始程式碼檔案並追蹤錯誤的原因。

16. 要想打開錯誤清單視窗,你只要在 View 功能表中, 定位 Other Windows 並選擇 Error List

**Visual Studio 2010:**在 View 功能表中選擇 **Error List,**來打開錯誤清單視窗。


圖例 14
錯誤清單視窗顯示編譯過程中產生的錯誤

17. 驗證部署的目標設備是 Windows Phone 模擬器。要做到這一點,確保在工具列 Start Debugging 按鈕旁邊的 Select Device 下拉清單中選擇 Windows Phone 7 模擬器。


圖例 15
選擇目標設備來部署應用

**說明:**當你從 Visual Studio 中部署你的應用程式時,你可以選擇部署到真實的設備上還是 Windows Phone 模擬器。

18. 在 Windows Phone 模擬器中按 F5 執行應用程式 ,會出現一個設備模擬器視窗,當 Visual Studio 搭建模擬器環境並部署程式時,它會處於暫停狀態。一旦準備完畢,模擬器會展示開始介面,片刻之後,你的應用程式便會顯示在模擬器視窗中。

應用程式將會顯示一個不包括任何東西的簡單藍色介面。在這個初期階段是正常的。


圖例 16
在 Windows Phone 模擬器中執行應用程式

你基本不能對應用程式進行操作,直到你建立了使用者介面並編寫了應用程式的邏輯。

19. 按下 SHIFT + F5 或者在工具列上點擊 Stop 按鈕 來拆卸除錯器並結束除錯程序。不要關閉模擬器視窗。


圖例 17
結束除錯程序

說明: 開啟一個除錯程序,會在模擬環境搭建和應用程式執行上花費一些時間。為了簡化除錯體驗,請不要在 Visual Studio 裡處理原始程式碼時關閉模擬器。一旦模擬器啟動,它將會用很少的時間停止當前程序,編輯原始程式碼,然後編譯並部署一個新版的應用程式來啟動一個新的除錯程序。


任務 2 – XNA Framework 遊戲資源

很多遊戲使用預定於的圖像呈現遊戲,處理聲音並用其他資源作為遊戲的一部分。本次動手實驗提供了一些這樣的資源使遊戲開發過程更簡單。在本次任務中,你將會把那些資源添加到遊戲中去。本次操作還提供了一些程式碼檔案來處理功能表的複雜性和遊戲過程中的螢幕變換。你也將會把那些檔案添加到遊戲中。

你正在建置遊戲。這個遊戲將顯示一些圖像。在本次任務中,你將要添加一些資源到應用程式和一些現有的遊戲邏輯中(ScreenManager 類別)。

**說明:**所有提供的遊戲資源都放在在本次動手實驗的安裝目錄下。位於以下位置:

Source \ Assets \ Code – 所有 CSharp 程式碼檔案。

Source \ Assets \ Media – 所有圖像,字體和聲音。

1. 在 Visual Studio 裡關閉專案。切換到 Windows Explorer,找到專案路徑位置並從Source \ Assets \ Media \ Images \ Icons 資料夾中拷貝兩個檔案到 “AlienGame” 目錄,替換以下檔案:

  • Game.ico
  • GameThumbnail.png

2. 重新打開 Visual Studio 2010 並打開 AlienGame 專案。

大多數遊戲在模型,網格,精靈,紋理,效果,地形,動畫等方面採用藝術方式。這些藝術作品可以用很多種方式製作出來並存成多種不同的格式。在遊戲開發的過程中,這些作品往往會被頻繁地修改。內容管道的宗旨就是説明你把這些藝術作品輕鬆自動地添加到你的遊戲中。一個正在製作小汽車模型的藝術家可以把作品檔案添加到 XNA Game Studio  遊戲專案中,分配模型名稱,為它選擇一個導入器和內容處理器。然後,想讓小汽車開動起來的開發人員可以透過叫用 ContentManager.Load 來載入它。這種簡單的流程讓藝術家專注於素材的製作,讓開發人員專注於素材的使用,而不需要花時間擔心內容轉換的問題。

 XNA 內容管道可以很容易的整合到你的 XNA Game Studio 專案中。你只需要添加資源到你的專案中,當你編譯的時候,內容導入器將會導入這些資料並轉換成一個 XNB (XNA 的二進位檔案案)。XNB 檔案是根據平臺類別型產生的。內容導入器可以看做是一個編譯庫。除了 XNA Game Studio 提供的標準版,你還可以使用自己或協力廠商開發的導入器和處理器。一些標準的內容導入器包括以下檔案案類別型 (部分清單):

  • Autodesk FBX format (.fbx)。
  • DirectX Effect file format (.fx)。
  • 字體描述的設定在一個.spritefont 的文件裡。
  • 圖案檔案支援以下類別型:.bmp,dds,dib,.hdr,.jpg,.pfm,.png,.ppm 和 .tga。
  • 遊戲聲音的設定在Microsoft Cross-Platform Audio Creation Tool (XACT) format (.xap) 裡。

3. 本次操作提供了一些多媒體資料,比如字體,聲音和圖像。把以下專案添加到 AlienGameContent 專案中:

  • 所有的字體來源於 Source \ Assets \ Media \ Fonts 資料夾。
  • 所有的圖像來源於 Source \ Assets \ Media \ Images \ Content 資料夾。
  • 所有的聲音來源於 Source \ Assets \ Media \ Sounds 資料夾。

4. 透過右鍵點擊 AlienGameContent 專案名稱並選擇 Add | Existing Items 來添加已有的項目:


圖例 18
添加已有的項目到功能中

5. 沿著檔案路徑位址並選擇檔案。在某些情況下,你會看不到所有的檔案。這時,在檔案選擇對話方塊中更換篩檢選項來顯示特定目錄下的所有檔案:


圖例 19
更改檔案類型篩檢選項來顯示資料夾中的所有檔案

6. AlienGameContent 專案完成的樣子如下:


圖例 20
專案的結構內容

7. 添加一個新的專案資料夾 – 以右鍵點擊 AlienGame,選擇 Add | New Folder


圖例 21
添加新的專案資料夾

8. 把資料夾命名為 ScreenManager

此資料夾將存放本次操作所提供的原始檔案。這些檔案將會有助於管理遊戲介面和功能表的建立和更改的複雜性。

**說明:**此程式碼實現了建立 XNA Game Studio 功能表和介面的標準方法。

9. 從 Source \ Assets \ Code \ ScreenManager 資料夾中把所有已有的介面管理檔案添加到這個資料夾中。

10. 從 Source \ Assets \ Code 資料夾中把 ParticleSystem.cs 檔案添加到 AlienGame 專案的根目錄。

11. 專案結果如下:


圖例 22
完成後的 AlienGame 專案結構

12. 添加一個新的類別到AlienGame 專案裡並命名為 BackgroundScreen:


圖例 23
添加一個新的類別到專案裡

13. 打開這個新類別,把下面的內容以宣告的方式添加到類別中:

(程式碼片段 – XNA 進行遊戲開發– Background Screen 使用的宣告)

C#

using AlienGameSample;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

14. 從 GameScreen 類別中衍生一個新類別 (GameScreen 類別是在以前添加到 ScreenManager 資料夾類別裡定義的):

C#

class BackgroundScreen : GameScreen
{
}

15. 添加以下類別變數為以後載入資源使用:

(程式碼片段 – 用 XNA 進行遊戲開發– Background Screen 類別變數)

C#

Texture2D title;
Texture2D background;

16. 定義類別的建構函數如下:

(程式碼片段 – 用 XNA 進行遊戲開發– Background Screen 建構函數)

C#

public BackgroundScreen()
{
   TransitionOnTime = TimeSpan.FromSeconds(0.0);
   TransitionOffTime = TimeSpan.FromSeconds(0.5);
}

17. 根據練習前言描述的情況,GameScreen 類別定義了一些遊戲核心功能:載入資源,更新和繪圖。 覆寫基於 LoadContent 類別的功能:

(程式碼片段 – 用 XNA 進行遊戲開發 – Background Screen LoadContent 方法)

C#

public override void LoadContent()
{
    title = ScreenManager.Game.Content.Load<Texture2D>("title");
    background = ScreenManager.Game.Content.Load<Texture2D>("background");
}

這個程式碼片段從遊戲資源中載入內容。內容是根據名稱載入的。

18. 現在建立一個 LoadingScreen 類別。當遊戲資源載入時,將會顯示帶類別的介面。

**說明:**要建立一個新類別,在 Solution Explorer 裡右鍵點擊 AlienGame 專案並選擇 Add | Class

19. 添加以下宣告到 LoadingScreen 類別中:

(程式碼片段 – 用 XNA 進行遊戲開發 – Loading Screen使用的宣告)

C#

using AlienGameSample;
using System.Threading;
using Microsoft.Xna.Framework;

20. 從基本類別 GameScreen 中衍生一個新類別 (和以前的操作一樣),並添加如下的建構函數:

(程式碼片段 – 用 XNA 進行遊戲開發 –  Loading Screen 的建構函數)

C#

class LoadingScreen : GameScreen
{
    public LoadingScreen()
    {
        TransitionOnTime = TimeSpan.FromSeconds(0.0);
        TransitionOffTime = TimeSpan.FromSeconds(0.0);
    }
}

21. 添加一個類別變數來控制用於載入元件的執行緒:

(程式碼片段 – 用 XNA 進行遊戲開發 – Loading Screen 的類別變數)

C#

private Thread backgroundThread;

22. 建立一個載入內容的方法。這個方法是 XNA 程式設計中標準裝載程式的一部分:

(程式碼片段 – 用 XNA 進行遊戲開發 – Loading Screen BackgroundLoadContent 方法)

C#

void BackgroundLoadContent()
{
   ScreenManager.Game.Content.Load<object>("alien_hit");
   ScreenManager.Game.Content.Load<object>("alien1");
   ScreenManager.Game.Content.Load<object>("background");
   ScreenManager.Game.Content.Load<object>("badguy_blue");
   ScreenManager.Game.Content.Load<object>("badguy_green");
   ScreenManager.Game.Content.Load<object>("badguy_orange");
   ScreenManager.Game.Content.Load<object>("badguy_red");
   ScreenManager.Game.Content.Load<object>("bullet");
   ScreenManager.Game.Content.Load<object>("cloud1");
   ScreenManager.Game.Content.Load<object>("cloud2");
   ScreenManager.Game.Content.Load<object>("fire");
   ScreenManager.Game.Content.Load<object>("gamefont");
   ScreenManager.Game.Content.Load<object>("ground");
   ScreenManager.Game.Content.Load<object>("hills");
   ScreenManager.Game.Content.Load<object>("laser");
   ScreenManager.Game.Content.Load<object>("menufont");
   ScreenManager.Game.Content.Load<object>("moon");
   ScreenManager.Game.Content.Load<object>("mountains_blurred");
   ScreenManager.Game.Content.Load<object>("player_hit");
   ScreenManager.Game.Content.Load<object>("scorefont");
   ScreenManager.Game.Content.Load<object>("smoke");
   ScreenManager.Game.Content.Load<object>("sun");
   ScreenManager.Game.Content.Load<object>("tank");
   ScreenManager.Game.Content.Load<object>("tank_fire");
   ScreenManager.Game.Content.Load<object>("tank_tire");
   ScreenManager.Game.Content.Load<object>("tank_top");
   ScreenManager.Game.Content.Load<object>("title");
   ScreenManager.Game.Content.Load<object>("titlefont");
}

23. 覆寫父類別裡的 LoadContent 方法並且使用一個新的執行緒啟動載入器的方法來實現資源的非同步載入:

**說明:**在我們這個簡單的遊戲中,資源會在頃刻之間載入完畢,但是對於複雜的遊戲,這個方法可以顯示一個進度指示器或啟動畫面。

(程式碼片段 – 用 XNA 進行遊戲開發 – Loading Screen LoadContent方法)

C#

public override void LoadContent()
{
   if (backgroundThread == null)
   {
      backgroundThread = new Thread(BackgroundLoadContent);
      backgroundThread.Start();
   }

   base.LoadContent();
}

24. 覆寫父類別裡的 Update 方法來等待 LoadContent 結束並跳到主功能表螢幕 (添加接下來的步驟):

(程式碼片段 – 用 XNA 進行遊戲開發 – Loading Screen Update 方法)

C#

public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool   coveredByOtherScreen)
{
    if (backgroundThread != null && backgroundThread.Join(10))
    {
        backgroundThread = null;
        this.ExitScreen();
        ScreenManager.AddScreen(new MainMenuScreen());
        ScreenManager.Game.ResetElapsedTime();
    }

    base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}

25. 現在添加一個新類別並命名為 MainMenuScreen。建立的時候,添加如下宣告:

(程式碼片段 – 用 XNA 進行遊戲開發 – MainMenu Screen 使用的宣告)

C#

using AlienGameSample;

26. 從 MenuScreen 父類別中衍生一個新類別。這個類別也是在添加到 ScreenManager 資料夾中類別裡定義的,用來方便處理需要顯示 / 互動功能表和功能表項目的所有典型功能。

27. 建立 MainMenuScreen 類別的建構函數:

(程式碼片段 – 用 XNA 進行遊戲開發 MainMenu Screen 的建構函數)

C#

class MainMenuScreen : MenuScreen
{
    public MainMenuScreen()
        : base("Main")
    {
        // Create our menu entries.
        MenuEntry startGameMenuEntry = new MenuEntry("START GAME");
        MenuEntry exitMenuEntry = new MenuEntry("QUIT");

        // Hook up menu event handlers.
        startGameMenuEntry.Selected += StartGameMenuEntrySelected;
        exitMenuEntry.Selected += OnCancel;

        // Add entries to the menu.
        MenuEntries.Add(startGameMenuEntry);
        MenuEntries.Add(exitMenuEntry);
    }
}

28. 在建構函數裡,你要訂閱兩個事件,它們會在玩家選擇選單項目時觸發。建立事件控制碼方法來管理這些事件:

(程式碼片段  – 用 XNA 進行遊戲開發 – MainMenu Screen 事件控制碼)

C#

void StartGameMenuEntrySelected(object sender, EventArgs e)
{
   
}

protected override void OnCancel()
{
   ScreenManager.Game.Exit();
}

29. 打開 AlienGame.cs 檔案並添加以下宣告:

(程式碼片段 – 用 XNA 進行遊戲開發 – AlienGame使用的宣告)

C#

using AlienGameSample;

30. 添加以下類別變數:

(程式碼片段 – 用 XNA 進行遊戲開發 – AlienGame 變數)

C#

ScreenManager screenManager;

31. 刪除 Visual Studio 建立的 spriteBatch 變數:

C#

SpriteBatch spriteBatch;

32. 刪除除類別建構函數和變數宣告之外的所有程式碼。

33. 在初始化主遊戲類別之後,你需要載入遊戲資源並顯示功能表玩家的功能表背景。功能表螢幕會在載入進度之後出現。此外,我們建議你在圖形設備上定義首選方案。為了達到這個目的,根據下面的程式碼片段,修改建構函數裡的方法:

(程式碼片段 – 用 XNA 進行遊戲開發 – AlienGame建構函數)

C#

public AlienGame()
{
   graphics = new GraphicsDeviceManager(this);

   //Set the Windows Phone screen resolution
   graphics.PreferredBackBufferWidth = 480;
   graphics.PreferredBackBufferHeight = 800;

   Content.RootDirectory = "Content";

   // Frame rate is 30 fps by default for Windows Phone.
   TargetElapsedTime = TimeSpan.FromSeconds(1 / 30.0);

   //Create a new instance of the Screen Manager
   screenManager = new ScreenManager(this);
   Components.Add(screenManager);

   //Add two new screens
   screenManager.AddScreen(new BackgroundScreen());
   screenManager.AddScreen(new LoadingScreen());
}

34. 編譯並執行應用程式。在應用程式載入完之後,主功能表螢幕便會顯示:


圖例 24
執行帶主功能表 的應用程式

35. 停止除錯並返回應用編輯狀態。

在這次任務中,你向遊戲中添加了提供的資源,建立了一些螢幕來顯示載入過程中的基本使用者介面並建立了一個主功能表。


任務 3 – XNA Game Studio 遊戲主迴圈 (Game Loop)

在本任務中,你將專注於遊戲剩下的兩個部分 – 覆寫 Update Draw 的功能。

1. 打開 BackgroundScreen.cs 文件。

2. 覆寫父類別的 Update 方法如下:

(程式碼片段 – 用 XNA 進行遊戲開發 – Background Screen Update 方法)

C#

public override void Update(GameTime gameTime, bool otherScreenHasFocus, 
                     bool coveredByOtherScreen)
{
   base.Update(gameTime, otherScreenHasFocus, false);
}

3. 覆寫父類別的 Draw 方法。方法 Draw 將使用 Microsoft.Xna.Framewok.Graphics 命名空間的 SpriteBatch 類別在圖像設備上繪製。它使用了一組相同的設定的精靈。更改 Draw 方法來匹配以下的程式碼片段:

(程式碼片段 – 用 XNA 進行遊戲開發*– Background Screen Draw* 方法)

C#

public override void Draw(GameTime gameTime)
{
   SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

   // Make the menu slide into place during transitions, using a
   // power curve to make things look more interesting (this makes
   // the movement slow down as it nears the end).
   float transitionOffset =         (float)Math.Pow(TransitionPosition, 2);

   spriteBatch.Begin();

   // Draw Background
   spriteBatch.Draw(background, new Vector2(0, 0), 
        new Color(255, 255, 255, TransitionAlpha));

   // Draw Title
   spriteBatch.Draw(title, new Vector2(60, 55), 
        new Color(255, 255, 255, TransitionAlpha));

   spriteBatch.End();
}

4. 按 F5 進行編譯並執行應用程式。


圖例 25
更改 Update 和 Draw 方法後,執行應用程式

5. 停止除錯  (SHIFT + F5) 並返回編輯狀態。

6. 給應用程式添加一個類別,並命名為 GameplayScreen。

**說明:**要建立一個新類別,在 Solution Explorer 裡以右鍵點擊 AlienGame 專案並選擇 Add | Class

7. 在新類別裡添加以下宣告:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen使用的宣告)

C#

using AlienGameSample;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using System.IO.IsolatedStorage;
using System.IO;

8. 從 GameScreen 類別裡衍生類別。

C#

class GameplayScreen : GameScreen
{
}

9. 添加以下類別變數 (你將會在遊戲中用到它們)。在後續的操作中,我們會使用那些變數來處理遊戲邏輯,使用者輸入,繪圖等:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen 變數)

C#

//
// Game Play Members
//
Rectangle worldBounds;
bool gameOver;
int baseLevelKillCount;
int levelKillCount;
float alienSpawnTimer;
float alienSpawnRate;
float alienMaxAccuracy;
float alienSpeedMin;
float alienSpeedMax;
int alienScore;
int nextLife;
int hitStreak;
int highScore;
Random random;

//
// Rendering Members
//
Texture2D cloud1Texture;
Texture2D cloud2Texture;
Texture2D sunTexture;
Texture2D moonTexture;
Texture2D groundTexture;
Texture2D tankTexture;
Texture2D alienTexture;
Texture2D badguy_blue;
Texture2D badguy_red;
Texture2D badguy_green;
Texture2D badguy_orange;
Texture2D mountainsTexture;
Texture2D hillsTexture;
Texture2D bulletTexture;
Texture2D laserTexture;

SpriteFont scoreFont;
SpriteFont menuFont;

Vector2 cloud1Position;
Vector2 cloud2Position;
Vector2 sunPosition;

// Level changes, nighttime transitions, etc
float transitionFactor; // 0.0f == day, 1.0f == night
float transitionRate; // > 0.0f == day to night

ParticleSystem particles;

//
// Audio Members
//
SoundEffect alienFired;
SoundEffect alienDied;
SoundEffect playerFired;
SoundEffect playerDied;

//Screen dimension consts
const float screenHeight = 800.0f;
const float screenWidth = 480.0f;
const int leftOffset = 25;
const int topOffset = 50;
const int bottomOffset = 20;

10. GamePlay 類別的建構函式定義了螢幕轉換的速度 (在 Gameplay 螢幕和遊戲的其他螢幕之間),並且還定義了“遊戲世界”的大小 - 處理遊戲活動的地方。添加這個的類別建構函數如下:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen建構函數)

C#

public GameplayScreen()
{
   random = new Random();

   worldBounds = new Rectangle(0, 0, (int)screenWidth, (int)screenHeight);

   gameOver = true;

   TransitionOnTime = TimeSpan.FromSeconds(0.0);
   TransitionOffTime = TimeSpan.FromSeconds(0.0);
}

11. 現在讓我們建立內容載入和卸載的功能。覆寫父類別的 LoadContentUnloadContent 方法,添加 LoadContent 的程式碼片段:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen LoadContent方法)

C#

public override void LoadContent()
{
    cloud1Texture = ScreenManager.Game.Content.Load<Texture2D>("cloud1");
    cloud2Texture = ScreenManager.Game.Content.Load<Texture2D>("cloud2");
    sunTexture = ScreenManager.Game.Content.Load<Texture2D>("sun");
    moonTexture = ScreenManager.Game.Content.Load<Texture2D>("moon");
    groundTexture = ScreenManager.Game.Content.Load<Texture2D>("ground");
    tankTexture = ScreenManager.Game.Content.Load<Texture2D>("tank");
    mountainsTexture = ScreenManager.Game.Content.Load<Texture2D>("mountains_blurred");
    hillsTexture = ScreenManager.Game.Content.Load<Texture2D>("hills");
    alienTexture = ScreenManager.Game.Content.Load<Texture2D>("alien1");
    badguy_blue = ScreenManager.Game.Content.Load<Texture2D>("badguy_blue");
    badguy_red = ScreenManager.Game.Content.Load<Texture2D>("badguy_red");
    badguy_green = ScreenManager.Game.Content.Load<Texture2D>("badguy_green");
    badguy_orange = ScreenManager.Game.Content.Load<Texture2D>("badguy_orange");
    bulletTexture = ScreenManager.Game.Content.Load<Texture2D>("bullet");
    laserTexture = ScreenManager.Game.Content.Load<Texture2D>("laser");
    alienFired = ScreenManager.Game.Content.Load<SoundEffect>("Tank_Fire");
    alienDied = ScreenManager.Game.Content.Load<SoundEffect>("Alien_Hit");
    playerFired = ScreenManager.Game.Content.Load<SoundEffect>("Tank_Fire");
    playerDied = ScreenManager.Game.Content.Load<SoundEffect>("Player_Hit");
    scoreFont = ScreenManager.Game.Content.Load<SpriteFont>("ScoreFont");
    menuFont = ScreenManager.Game.Content.Load<SpriteFont>("MenuFont");

    cloud1Position = new Vector2(224 - cloud1Texture.Width, 32);
    cloud2Position = new Vector2(64, 80);

    sunPosition = new Vector2(16, 16);

    particles = new ParticleSystem(ScreenManager.Game.Content, ScreenManager.SpriteBatch);

    base.LoadContent();
}

12. 添加 UnloadContent 的程式碼片段:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen Unload 方法)

C#

public override void UnloadContent()
{
    particles = null;

    base.UnloadContent();
}

13. 覆寫父類別 Update 的功能:

**說明:**這個方法將會被修改來提供遊戲邏輯。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen Update方法)

C#

/// <summary>
/// Runs one frame of update for the game.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime, 
    bool otherScreenHasFocus, bool coveredByOtherScreen)
{
   float elapsed =  (float)gameTime.ElapsedGameTime.TotalSeconds;

   base.Update(gameTime, otherScreenHasFocus,   coveredByOtherScreen);
}

14. 覆寫父類別 Draw 的功能使當前“遊戲世界”的繪製速度達到每秒 30 次。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen Draw區域)

C#

/// <summary>
/// Draw the game world, effects, and HUD
/// </summary>
/// <param name="gameTime">The elapsed time since last Draw</param>
public override void Draw(GameTime gameTime)
{
   float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

   ScreenManager.SpriteBatch.Begin();

   ScreenManager.SpriteBatch.End();
}

**說明:**GameTime 用於計算各種遊戲項目的繪製位置。

15. 打開 MainMenuScreen.cs 檔案,找到 StartGameMenuEntrySelected 方法。這個方法應該沒有內容,添加如下程式碼。當玩家點擊“START GAME”按鈕時,它將會在 ScreenManager 裡添加 GameplayScreen螢幕

(程式碼片段 – 用 XNA 進行遊戲開發 – MainMenu Screen – GameMenuEntrySelected  控制碼)

C#

void StartGameMenuEntrySelected(object sender, EventArgs e)
{
    ScreenManager.AddScreen(new GameplayScreen());
}

16. 編譯並執行應用程式。點擊“START GAME” 功能表選項,你可以看到主功能表選項在螢幕上向下滾動。


圖例 26
執行遊戲

**說明:**GameplayScreen 仍然是空的,因此在遊戲開發的當前階段你將看不到任何變化。

17. 停止除錯並返回編輯狀態。

在這個任務中,你建立了一個主遊戲類別和覆寫了遊戲的基本功能。


任務 4 – XNA Game Studio 遊戲輸入

在這個任務中,你將為遊戲添加一個輸入。在 Window Phone 上,輸入是透過觸控式螢幕和加速器完成的。由於 Windows Phone 模擬器不支援加速器,本次實驗採用鍵盤方案來類別比和替代加速器的功能。它只能在模擬器上工作,不能在真實的設備上使用。

1. 添加一個 Microsoft.Device.Sensors 編譯庫的引用。


圖例 27
添加一個 Microsoft.Devices.Sensors 組件的引用

**說明:**要添加一個引用,在 Solution Explorer 裡,右鍵點擊 References 節點,在 AlienGame 專案下並選擇 Add References

2. 打開 GameplayScreen.cs檔案  (如果沒有打開的話)。

3. 向類別中添加如下額外的宣告:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen –額外的宣告)

C#

using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Devices.Sensors;

4. 添加類別變數來保存觸摸和加速器的狀態:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen –更多地類別變數)

C#

//Input Members
AccelerometerReadingEventArgs accelState;
TouchCollection touchState;
Accelerometer Accelerometer;

5. 初始化加速器並訂閱它的事件。為了達到這個目的,添加如下程式碼片段到類別的建構函數中:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – 加速器的初始化)

C#

Accelerometer = new Accelerometer();
if (Accelerometer.State == SensorState.Ready)
{
    Accelerometer.ReadingChanged += (s, e) =>
    {
        accelState = e;
    };
    Accelerometer.Start();
}

6. 在 GameplayScreen 類別中建立  “Input” 區域:

C#

#region Input
#endregion

7. 在 Input 區域裡覆寫父類別 HandleInput 的方法:

這個方法將會讀取當前玩家的輸入並在以後回應遊戲變數變化時會用到。

**說明:**在模擬器中,滑鼠點擊將被暴露成觸摸和鍵盤輸入。在 Windows Phone 的設備上,不會有鍵盤輸入發生。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplan Screen – HandleInput方法)

C#

/// <summary>
/// Input helper method provided by GameScreen.  Packages up the various input
/// values for ease of use.  Here it checks for pausing and handles controlling
/// the player's tank.
/// </summary>
/// <param name="input">The state of the gamepads</param>
public override void HandleInput(InputState input)
{
   if (input == null)
      throw new ArgumentNullException("input");

   if (input.PauseGame)
   {
      if (gameOver == true)
         finishCurrentGame();
   }
   else
   {
      touchState = TouchPanel.GetState();
      bool buttonTouched = false;

      //interpret touch screen presses
      foreach (TouchLocation location in touchState)
      {
         switch (location.State)
         {
            case TouchLocationState.Pressed:
               buttonTouched = true;
               break;
            case TouchLocationState.Moved:
               break;
            case TouchLocationState.Released:
               break;
         }
      }

    float movement = 0.0f;
    if (accelState != null)
    {
        if (Math.Abs(accelState.X) > 0.10f)
        {
            if (accelState.X > 0.0f)
                movement = 1.0f;
            else
                movement = -1.0f;
        }
    }

      //TODO: Update player Velocity over X axis #1
      
      //This section handles tank movement.  We only allow one "movement" action
      //to occur at once so that touchpad devices don't get double hits.
      KeyboardState keyState = Keyboard.GetState();

      if (input.CurrentGamePadStates[0].DPad.Left ==    ButtonState.Pressed || keyState.IsKeyDown(Keys.Left))
      {
         //TODO: Update player velocity over X axis #2

      }
      else if (input.CurrentGamePadStates[0].DPad.Right ==  ButtonState.Pressed || keyState.IsKeyDown(Keys.Right))
      {
         //TODO: Update player velocity over X axis #3

      }
      else
      {
         //TODO: Update player velocity over X axis #4

      }

      // B button, or pressing on the upper half of the pad or space on keyboard or touching the touch panel fires the weapon.
      if (input.CurrentGamePadStates[0].IsButtonDown(Buttons.B) || input.CurrentGamePadStates[0].IsButtonDown(Buttons.A) || input.CurrentGamePadStates[0].ThumbSticks.Left.Y > 0.25f ||
keyState.IsKeyDown(Keys.Space) || buttonTouched)
      {
         if (!gameOver)
         {
            //TODO: Fire the bullet

         }
         else if (gameOver)
            finishCurrentGame();

      }
   }
}

8. 根據以下的程式碼片段,添加一個輔助方法來完成遊戲:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – finishCurrentGame 方法)

C#

private void finishCurrentGame()
{
   foreach (GameScreen screen in ScreenManager.GetScreens())
      screen.ExitScreen();

   ScreenManager.AddScreen(new BackgroundScreen());
   ScreenManager.AddScreen(new MainMenuScreen());
}

9. 編譯應用程式。

在本任務中,你為遊戲建立了一個輸入處理子系統。在接下來的任務裡,它將會被用於建立遊戲邏輯。


任務 5 – 外星人遊戲的具體邏輯

在這個任務中,你將建立遊戲的具體邏輯,説明方法和類別。

1. 在 GameplayScreen.cs 檔案裡,根據以下的程式碼片段建立一個新的輔助類別 (在GameplayScreen 類別的外面):

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Bullet 類別)

C#

/// <summary>
/// Represents either an alien or player bullet
/// </summary>
public class Bullet
{
   public Vector2 Position;
   public Vector2 Velocity;
   public bool IsAlive;
}

2. 在 Bullet 類別後添加兩個輔助類別:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Player和Alien類別)

C#

/// <summary>
/// The player's state
/// </summary>
public class Player
{
   public Vector2 Position;
   public Vector2 Velocity;
   public float Width;
   public float Height;
   public bool IsAlive;
   public float FireTimer;
   public float RespawnTimer;
   public string Name;
   public Texture2D Picture;
   public int Score;
   public int Lives;
}

/// <summary>
/// Data for an alien.  The only difference between the ships
/// and the badguys are the texture used.
/// </summary>
public class Alien
{
   public Vector2 Position;
   public Texture2D Texture;
   public Vector2 Velocity;
   public float Width;
   public float Height;
   public int Score;
   public bool IsAlive;
   public float FireTimer;
   public float Accuracy;
   public int FireCount;
}

3. 在 GameplayScreen 類別中添加以下類別變數:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – 更多地變數)

C#

Player player;
List<Alien> aliens;
List<Bullet> alienBullets;
List<Bullet> playerBullets;

4. 像下面程式碼片段展示的一樣,在類別的建構函數裡初始化這些變數:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Player Alien 的初始化)

C#

public GameplayScreen()
{
    ...

    player = new Player();
    playerBullets = new List<Bullet>();

    aliens = new List<Alien>();
    alienBullets = new List<Bullet>();

    Accelerometer = new Accelerometer();
    if (AccelerometerSensor.Default.State == SensorState.Ready)
    {
        ...
    }
    ...
}

5. 在 LoadContent 方法中初始化玩家變數Width 和Height (在初始化 ParticleSystem 之後):

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – 在LoadContent 方法中初始化 Player)

C#

public override void LoadContent()
{
    ...

    particles = new ParticleSystem(ScreenManager.Game.Content, ScreenManager.SpriteBatch);
    player.Width = tankTexture.Width;
    player.Height = tankTexture.Height;

    base.LoadContent();
}

6. 下面的幾個程式碼片段將添加遊戲的邏輯。它們將根據玩家的輸入更改“player1”的路線。找到 HandleInput 方法定位到下面的這行:

C#

//TODO: Update player Velocity over X axis #1

在它之後添加以下程式碼:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – HandleInput 方法中的 Player Movements 1)

C#

player.Velocity.X = movement;

7. 找到下面這行 (在 HandleInput 方法中):

C#

//TODO: Update player velocity over X axis #2

在它之後添加如下程式碼片段:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen –HandleInput方法中的Player Movements 2)

C#

player.Velocity.X = -1.0f;

8. 找到下面這行 (在 HandleInput 方法中):

C#

//TODO: Update player velocity over X axis #3

在它之後添加如下程式碼片段:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen –HandleInput method 方法中的 Player Movements 3 )

C#

player.Velocity.X = 1.0f;

9. 找到下面這行 (在 HandleInput 方法中):

C#

//TODO: Update player velocity over X axis #4

在它之後添加如下程式碼片段:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen –HandleInput method 方法中的 Player Movements 4)

C#

player.Velocity.X = MathHelper.Min(input.CurrentGamePadStates[0].ThumbSticks.Left.X * 2.0f, 1.0f);

10. 找到下面這一行:

C#

//TODO: Fire the bullet

根據下面的程式碼片段更改 “if” 語句:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – 輸入觸發子彈的程式碼)

C#

if (player.FireTimer <= 0.0f && player.IsAlive && !gameOver)
{
    Bullet bullet = CreatePlayerBullet();
    bullet.Position = new Vector2((int)(player.Position.X + player.Width / 2) - bulletTexture.Width / 2, player.Position.Y - 4);
    bullet.Velocity = new Vector2(0, -256.0f);
    player.FireTimer = 1.0f;

    particles.CreatePlayerFireSmoke(player);
    playerFired.Play();
}
else if (gameOver)
    finishCurrentGame();

11. 在 GameplayScreen 類別裡建立如下方法:

這個方法將建立一個 Bullet 類別的實例 (以前定義過)。這個實例將會在以前的一個程式碼片段裡被用到。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – CreatePlayerBullet 方法)

C#

/// <summary>
/// Returns an instance of a usable player bullet.  Prefers reusing an /// existing (dead)
/// bullet over creating a new instance.
/// </summary>
/// <returns>A bullet ready to place into the world.</returns>
Bullet CreatePlayerBullet()
{
   Bullet b = null;

   for (int i = 0; i < playerBullets.Count; ++i)
   {
      if (playerBullets[i].IsAlive == false)
      {
         b = playerBullets[i];
         break;
      }
   }

   if (b == null)
   {
      b = new Bullet();
      playerBullets.Add(b);
   }

   b.IsAlive = true;

   return b;
}

12. 更改 Update 方法。在 “base.Update(…)” 方法調用之前,添加如下反白藍色的程式碼片段 :

這塊程式碼實際上提供了“遊戲邏輯” – 它移動玩家並調用方法來更新外星人和子彈的位置。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Update 方法)

C#

public override void Update(GameTime gameTime,
    bool otherScreenHasFocus, bool coveredByOtherScreen)
{
    float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

    if (IsActive)
    {
        // Move the player
        if (player.IsAlive == true)
        {
            player.Position += player.Velocity * 128.0f * elapsed;
            player.FireTimer -= elapsed;

            if (player.Position.X <= 0.0f)
                player.Position = new Vector2(0.0f, player.Position.Y);

            if (player.Position.X + player.Width >= worldBounds.Right)
                player.Position = new Vector2(worldBounds.Right - player.Width, player.Position.Y);
        }

        Respawn(elapsed);

        UpdateAliens(elapsed);

        UpdateBullets(elapsed);

        CheckHits();

        if (player.IsAlive && player.Velocity.LengthSquared() > 0.0f)
            particles.CreatePlayerDust(player);

        particles.Update(elapsed);
    }

    base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}

13. 在 GameplayScreen 類別裡,添加如下輔助方法:

**說明:**下面的程式碼片段添加了一些輔助方法。方法的目的如下:

Respawn:檢查是不是玩家“死了”但遊戲沒有結束。如果是這種情況,它會等 respawnTimer 完成並在螢幕中央建立一個新的玩家實例。

UpdateBullets:檢查並更新玩家和外星人子彈在螢幕上的位置。

UpdateAliens:移動外星人並計算它們應不應該發射子彈並朝哪個方向發射。

CheckHits:檢查所有子彈和玩家 / 外星人的碰撞。此外,當碰撞發生,它會處理遊戲邏輯,比如殺掉人物,添加分數,結束遊戲等。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Helper update方法)

C#

/// <summary>
/// Handles respawning the player if we are playing a game and the player is dead.
/// </summary>
/// <param name="elapsed">Time elapsed since Respawn was called last.</param>
void Respawn(float elapsed)
{
    if (gameOver)
        return;

    if (!player.IsAlive)
    {
        player.RespawnTimer -= elapsed;
        if (player.RespawnTimer <= 0.0f)
        {
            // See if there are any bullets close...
            int left = worldBounds.Width / 2 - tankTexture.Width / 2 - 8;
            int right = worldBounds.Width / 2 + tankTexture.Width / 2 + 8;

            for (int i = 0; i < alienBullets.Count; ++i)
            {
                if (alienBullets[i].IsAlive == false)
                    continue;

                if (alienBullets[i].Position.X >= left || alienBullets[i].Position.X <= right)
                    return;
            }

            player.IsAlive = true;
            player.Position = new Vector2(worldBounds.Width / 2 - player.Width / 2, worldBounds.Bottom - groundTexture.Height + 2 - player.Height);
            player.Velocity = Vector2.Zero;
            player.Lives--;
        }
    }
}

/// <summary>
/// Moves all of the bullets (player and alien) and prunes "dead" bullets.
/// </summary>
/// <param name="elapsed"></param>
void UpdateBullets(float elapsed)
{
    for (int i = 0; i < playerBullets.Count; ++i)
    {
        if (playerBullets[i].IsAlive == false)
            continue;

        playerBullets[i].Position += playerBullets[i].Velocity * elapsed;

        if (playerBullets[i].Position.Y < -32)
        {
            playerBullets[i].IsAlive = false;
            hitStreak = 0;
        }
    }

    for (int i = 0; i < alienBullets.Count; ++i)
    {
        if (alienBullets[i].IsAlive == false)
            continue;

        alienBullets[i].Position += alienBullets[i].Velocity * elapsed;

        if (alienBullets[i].Position.Y > worldBounds.Height - groundTexture.Height - laserTexture.Height)
            alienBullets[i].IsAlive = false;
    }
}

/// <summary>
/// Moves the aliens and performs their "thinking" by determining if they
/// should shoot and where.
/// </summary>
/// <param name="elapsed">The elapsed time since UpdateAliens was called last.</param>
private void UpdateAliens(float elapsed)
{
    // See if it's time to spawn an alien;
    alienSpawnTimer -= elapsed;
    if (alienSpawnTimer <= 0.0f)
    {
        SpawnAlien();
        alienSpawnTimer += alienSpawnRate;
    }

    for (int i = 0; i < aliens.Count; ++i)
    {
        if (aliens[i].IsAlive == false)
            continue;

        aliens[i].Position += aliens[i].Velocity * elapsed;
        if ((aliens[i].Position.X < -aliens[i].Width - 64 && aliens[i].Velocity.X < 0.0f) ||
            (aliens[i].Position.X > worldBounds.Width + 64 && aliens[i].Velocity.X > 0.0f))
        {
            aliens[i].IsAlive = false;
            continue;
        }

        aliens[i].FireTimer -= elapsed;

        if (aliens[i].FireTimer <= 0.0f && aliens[i].FireCount > 0)
        {
            if (player.IsAlive)
            {
                Bullet bullet = CreateAlienBullet();
                bullet.Position.X = aliens[i].Position.X + aliens[i].Width / 2 - laserTexture.Width / 2;
                bullet.Position.Y = aliens[i].Position.Y + aliens[i].Height;
                if ((float)random.NextDouble() <= aliens[i].Accuracy)
                {
                    bullet.Velocity = Vector2.Normalize(player.Position - aliens[i].Position) * 64.0f;
                }
                else
                {
                    bullet.Velocity = new Vector2(-8.0f + 16.0f * (float)random.NextDouble(), 64.0f);
                }

                alienFired.Play();
            }

            aliens[i].FireCount--;
        }
    }
}

/// <summary>
/// Performs all bullet and player/alien collision detection.  Also handles game logic
/// when a hit occurs, such as killing something, adding score, ending the game, etc.
/// </summary>
void CheckHits()
{
    if (gameOver)
        return;

    for (int i = 0; i < playerBullets.Count; ++i)
    {
        if (playerBullets[i].IsAlive == false)
            continue;

        for (int a = 0; a < aliens.Count; ++a)
        {
            if (aliens[a].IsAlive == false)
                continue;

            if ((playerBullets[i].Position.X >= aliens[a].Position.X && playerBullets[i].Position.X <= aliens[a].Position.X + aliens[a].Width) && (playerBullets[i].Position.Y >= aliens[a].Position.Y && playerBullets[i].Position.Y <= aliens[a].Position.Y + aliens[a].Height))
            {
                playerBullets[i].IsAlive = false;
                aliens[a].IsAlive = false;

                hitStreak++;

                player.Score += aliens[a].Score * (hitStreak / 5 + 1);

                if (player.Score > highScore)
                    highScore = player.Score;

                if (player.Score > nextLife)
                {
                    player.Lives++;
                    nextLife += nextLife;
                }

                levelKillCount--;
                if (levelKillCount <= 0)
                    AdvanceLevel();

                particles.CreateAlienExplosion(new Vector2(aliens[a].Position.X + aliens[a].Width / 2, aliens[a].Position.Y + aliens[a].Height / 2));

                alienDied.Play();
            }
        }
    }

    if (player.IsAlive == false)
        return;

    for (int i = 0; i < alienBullets.Count; ++i)
    {
        if (alienBullets[i].IsAlive == false)
            continue;

        if ((alienBullets[i].Position.X >= player.Position.X + 2 && alienBullets[i].Position.X <= player.Position.X + player.Width - 2) && (alienBullets[i].Position.Y >= player.Position.Y + 2 && alienBullets[i].Position.Y <= player.Position.Y + player.Height))
        {
            alienBullets[i].IsAlive = false;

            player.IsAlive = false;

            hitStreak = 0;

            player.RespawnTimer = 3.0f;
            particles.CreatePlayerExplosion(new Vector2(player.Position.X + player.Width / 2, player.Position.Y + player.Height / 2));

            playerDied.Play();

            if (player.Lives <= 0)
            {
                gameOver = true;
            }
        }

    }
}

/// <summary>
/// Advances the difficulty of the game one level.
/// </summary>
void AdvanceLevel()
{
    baseLevelKillCount += 5;
    levelKillCount = baseLevelKillCount;
    alienScore += 25;
    alienSpawnRate -= 0.3f;
    alienMaxAccuracy += 0.1f;
    if (alienMaxAccuracy > 0.75f)
        alienMaxAccuracy = 0.75f;

    alienSpeedMin *= 1.35f;
    alienSpeedMax *= 1.35f;

    if (alienSpawnRate < 0.33f)
        alienSpawnRate = 0.33f;

    if (transitionFactor == 1.0f)
    {
        transitionRate = -0.5f;
    }
    else
    {
        transitionRate = 0.5f;
    }
}

14. 在 GameplayScreen 類別中添加如下程式碼片段:

**說明:**下面的程式碼片段也添加了一些輔助方法。它們的作用如下:

CreateAlienBullet:建立一個外星人子彈的實例。它會被用於外星人向玩家開火。

SpawnAlien:初始化一個新的外星人的實例,設定初始位置,速度,選擇紋理顏色等。

CreateAlien:建立一個新外星人的實例並把它添加到外星人的集合中。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Helper aliens方法)

C#

/// <summary>
/// Returns an instance of a usable alien bullet.  Prefers reusing an existing (dead)
/// bullet over creating a new instance.
/// </summary>
/// <returns>A bullet ready to place into the world.</returns>
Bullet CreateAlienBullet()
{
    Bullet b = null;

    for (int i = 0; i < alienBullets.Count; ++i)
    {
        if (alienBullets[i].IsAlive == false)
        {
            b = alienBullets[i];
            break;
        }
    }

    if (b == null)
    {
        b = new Bullet();
        alienBullets.Add(b);
    }

    b.IsAlive = true;

    return b;
}

/// <summary>
/// Creates an instance of an alien, sets the initial state, and places it into the world.
/// </summary>
private void SpawnAlien()
{
    Alien newAlien = CreateAlien();

    if (random.Next(2) == 1)
    {
        newAlien.Position.X = -64.0f;
        newAlien.Velocity.X = random.Next((int)alienSpeedMin, (int)alienSpeedMax);
    }
    else
    {
        newAlien.Position.X = worldBounds.Width + 32;
        newAlien.Velocity.X = -random.Next((int)alienSpeedMin, (int)alienSpeedMax);
    }

    newAlien.Position.Y = 24.0f + 80.0f * (float)random.NextDouble();

    // Aliens
    if (transitionFactor > 0.0f)
    {
        switch (random.Next(4))
        {
            case 0:
                newAlien.Texture = badguy_blue;
                break;
            case 1:
                newAlien.Texture = badguy_red;
                break;
            case 2:
                newAlien.Texture = badguy_green;
                break;
            case 3:
                newAlien.Texture = badguy_orange;
                break;
        }
    }
    else
    {
        newAlien.Texture = alienTexture;
    }

    newAlien.Width = newAlien.Texture.Width;
    newAlien.Height = newAlien.Texture.Height;
    newAlien.IsAlive = true;
    newAlien.Score = alienScore;

    float duration = screenHeight / newAlien.Velocity.Length();

    newAlien.FireTimer = duration * (float)random.NextDouble();
    newAlien.FireCount = 1;

    newAlien.Accuracy = alienMaxAccuracy;
}

/// <summary>
/// Returns an instance of a usable alien instance. Prefers reusing an existing (dead)
/// alien over creating a new instance.
/// </summary>
/// <returns>An alien ready to place into the world.</returns>
Alien CreateAlien()
{
    Alien b = null;

    for (int i = 0; i < aliens.Count; ++i)
    {
        if (aliens[i].IsAlive == false)
        {
            b = aliens[i];
            break;
        }
    }

    if (b == null)
    {
        b = new Alien();
        aliens.Add(b);
    }

    b.IsAlive = true;

    return b;
}

15. 找到 Draw 的方法並在 Screen.SpriteBatch.Begin()Screen.SpriteBatch.End() 叫用之間添加下面藍色反白的程式碼片段:

 Draw 方法的這一更改將會叫用説明方法在螢幕上繪製 Update 方法計算出來的變更。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen  –  Draw 方法的更新)

C#

public override void Draw(GameTime gameTime)
{
    float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

    ScreenManager.SpriteBatch.Begin();

    DrawBackground(elapsedTime);
    DrawAliens();
    DrawPlayer();
    DrawBullets();
    particles.Draw();
    DrawForeground(elapsedTime);
    DrawHud();

    ScreenManager.SpriteBatch.End();
}

16. 在 Draw 方法後添加以下方法:

**說明:**下面的程式碼片段添加了一些和繪圖相關的輔助方法。它們的作用如下:

DrawPlayer:繪製玩家的坦克。

DrawAliens:繪製所有的外星人。

DrawBullets:繪製所有的子彈 (包括玩家的和外星人的)。

DrawForeground:繪製用於前景的雲團並移動它們。

DrawBackground:繪製草地,小山,山脈和太陽 / 月亮。而且還要處理白天黑夜的轉換。

DrawHud:繪製得分部分,剩餘的生命和必要時的“GAME OVER”。

DrawString:繪製文字陰影的泛型方法。

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Draw方法)

C#

/// <summary>
/// Draws the player's tank
/// </summary>
void DrawPlayer()
{
   if (!gameOver && player.IsAlive)
   {
      ScreenManager.SpriteBatch.Draw(tankTexture,               player.Position, Color.White);
   }
}

/// <summary>
/// Draws all of the aliens.
/// </summary>
void DrawAliens()
{
   for (int i = 0; i < aliens.Count; ++i)
   {
      if (aliens[i].IsAlive)                                    ScreenManager.SpriteBatch.Draw(aliens[i].Texture,           new Rectangle((int)aliens[i].Position.X,                           (int)aliens[i].Position.Y,                          (int)aliens[i].Width,                               (int)aliens[i].Height),                             Color.White);
   }
}

/// <summary>
/// Draw both the player and alien bullets.
/// </summary>
private void DrawBullets()
{
   for (int i = 0; i < playerBullets.Count; ++i)
   {
      if (playerBullets[i].IsAlive)
         ScreenManager.SpriteBatch.Draw(bulletTexture,              playerBullets[i].Position, Color.White);
   }

   for (int i = 0; i < alienBullets.Count; ++i)
   {
      if (alienBullets[i].IsAlive)
         ScreenManager.SpriteBatch.Draw(laserTexture,                   alienBullets[i].Position, Color.White);
   }
}

/// <summary>
/// Draw the foreground, which is basically the clouds. I think I had planned on one point
/// having foreground grass that was drawn in front of the tank.
/// </summary>
/// <param name="elapsedTime">The elapsed time since last Draw</param>
private void DrawForeground(float elapsedTime)
{
   // Move the clouds.  Movement seems like an Update thing to do, but this animations
   // have no impact over gameplay.
   cloud1Position += new Vector2(24.0f, 0.0f) * elapsedTime;
   if (cloud1Position.X > screenWidth)
      cloud1Position.X = -cloud1Texture.Width * 2.0f;

   cloud2Position += new Vector2(16.0f, 0.0f) * elapsedTime;
   if (cloud2Position.X > screenWidth)
      cloud2Position.X = -cloud1Texture.Width * 2.0f;

   ScreenManager.SpriteBatch.Draw(cloud1Texture,            cloud1Position, Color.White);
   ScreenManager.SpriteBatch.Draw(cloud2Texture,    cloud2Position, Color.White);
}

/// <summary>
/// Draw the grass, hills, mountains, and sun/moon. Handle transitioning
/// between day and night as well.
/// </summary>
/// <param name="elapsedTime">The elapsed time since last Draw</param>
private void DrawBackground(float elapsedTime)
{
   transitionFactor += transitionRate * elapsedTime;
   if (transitionFactor < 0.0f)
   {
      transitionFactor = 0.0f;
      transitionRate = 0.0f;
   }
   if (transitionFactor > 1.0f)
   {
      transitionFactor = 1.0f;
      transitionRate = 0.0f;
   }

   Vector3 day = Color.White.ToVector3();
   Vector3 night = new Color(80, 80, 180).ToVector3();
   Vector3 dayClear = Color.CornflowerBlue.ToVector3();
   Vector3 nightClear = night;

   Color clear = new Color(Vector3.Lerp(dayClear,                   nightClear, transitionFactor));
   Color tint = new Color(Vector3.Lerp(day, night,                  transitionFactor));

   // Clear the background, using the day/night color
   ScreenManager.Game.GraphicsDevice.Clear(clear);

   // Draw the mountains
   ScreenManager.SpriteBatch.Draw(mountainsTexture, new     Vector2(0, screenHeight - mountainsTexture.Height),             tint);

   // Draw the hills
   ScreenManager.SpriteBatch.Draw(hillsTexture, new     Vector2(0, screenHeight - hillsTexture.Height), tint);

   // Draw the ground
   ScreenManager.SpriteBatch.Draw(groundTexture, new    Vector2(0, screenHeight - groundTexture.Height),                tint);

   // Draw the sun or moon (based on time)
   ScreenManager.SpriteBatch.Draw(sunTexture, sunPosition,          new Color(255, 255, 255, (byte)(255.0f * (1.0f -                    transitionFactor))));
   ScreenManager.SpriteBatch.Draw(moonTexture, sunPosition,             new Color(255, 255, 255, (byte)(255.0f *                        transitionFactor)));
}

/// <summary>
/// Draw the hud, which consists of the score elements and the GAME OVER tag.
/// </summary>
void DrawHud()
{
   float scale = 2.0f;

   if (gameOver)
   {
      Vector2 size = menuFont.MeasureString("GAME OVER");
      DrawString(menuFont, "GAME OVER", new Vector2(ScreenManager.Game.GraphicsDevice.Viewport.Width / 2 - size.X, ScreenManager.Game.GraphicsDevice.Viewport.Height / 2 - size.Y / 2), new Color(255, 64, 64), scale);

   }
   else
   {
      int bonus = 100 * (hitStreak / 5);
      string bonusString = (bonus > 0 ? " (" + bonus.ToString(System.Globalization.CultureInfo.CurrentCulture) + "%)" : "");
      // Score
      DrawString(scoreFont, "SCORE: " +     player.Score.ToString(System.Globalization.CultureInfo  .CurrentCulture) + bonusString, new     Vector2(leftOffset, topOffset), Color.Yellow, scale);

     string text = "LIVES: " +  player.Lives.ToString(System.Globalization.CultureInfo  .CurrentCulture);
     Vector2 size = scoreFont.MeasureString(text);
     size *= scale;

     // Lives
     DrawString(scoreFont, text, new Vector2(screenWidth -          leftOffset - (int)size.X, topOffset),                   Color.Yellow,   scale);

     DrawString(scoreFont, "LEVEL: " + (((baseLevelKillCount - 5) / 5) + 1).ToString(System.Globalization.CultureInfo.CurrentCulture), new Vector2(leftOffset, screenHeight - bottomOffset), Color.Yellow, scale);

     text = "HIGH SCORE: " +    highScore.ToString(System.Globalization.CultureInfo
.CurrentCulture);

     size = scoreFont.MeasureString(text);

     DrawString(scoreFont, text, new Vector2(screenWidth -          leftOffset - (int)size.X * 2, screenHeight -            bottomOffset), Color.Yellow, scale);
   }
}

/// <summary>
/// A simple helper to draw shadowed text.
/// </summary>
void DrawString(SpriteFont font, string text, 
            Vector2 position, Color color)
{
   ScreenManager.SpriteBatch.DrawString(font, text, new     Vector2(position.X + 1, position.Y + 1), Color.Black);
   ScreenManager.SpriteBatch.DrawString(font, text,     position, color);
}

/// <summary>
/// A simple helper to draw shadowed text.
/// </summary>
void DrawString(SpriteFont font, string text, 
    Vector2 position, Color color, float fontScale)
{
   ScreenManager.SpriteBatch.DrawString(font, text, new     Vector2(position.X + 1, position.Y + 1), Color.Black,   0, new Vector2(0, font.LineSpacing / 2), fontScale,     SpriteEffects.None, 0);
   ScreenManager.SpriteBatch.DrawString(font, text,     position, color, 0, new Vector2(0, font.LineSpacing /   2), fontScale, SpriteEffects.None, 0);
}

17. 找到 LoadContent 方法並在 base.LoadContent() 方法叫用之後添加如下程式碼片段:

C#

public override void LoadContent()
{
    ...
    player.Width = tankTexture.Width;
    player.Height = tankTexture.Height;

    base.LoadContent();

    LoadHighscore();
    Start();
}

18. 找到 UnloadContent 方法並在 “particles = null;” 語句之前添加如下程式碼片段:

C#

public override void UnloadContent()
{
    SaveHighscore();

    particles = null;

    base.UnloadContent();
}

19. 建立一個帶有載入 / 卸載最高分數的邏輯區域。要向 Windows Phone 檔案案系統中保存 / 載入資料,你必須為每個應用程式使用單獨的儲存。在 GameplayScreen  類別裡添加如下程式碼片段來建立這個邏輯:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Highscore 存儲方法)

C#

#region Highscore loading/saving logic
/// <summary>
/// Saves the current highscore to a text file. The StorageDevice was selected during screen loading.
/// </summary>
private void SaveHighscore()
{
    using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream("highscores.txt", FileMode.Create, isf))
        {
            using (StreamWriter writer = new StreamWriter(isfs))
            {
                writer.Write(highScore.ToString(System.Globalization.CultureInfo.InvariantCulture));
                writer.Flush();
                writer.Close();
            }
        }
    }
}

/// <summary>
/// Loads the high score from a text file.  The StorageDevice was selected during the loading screen.
/// </summary>
private void LoadHighscore()
{
    using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (isf.FileExists("highscores.txt"))
        {
            using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream("highscores.txt", FileMode.Open, isf))
            {
                using (StreamReader reader = new StreamReader(isfs))
                {
                    try
                    {
                        highScore = Int32.Parse(reader.ReadToEnd(), System.Globalization.CultureInfo.InvariantCulture);
                    }
                    catch (FormatException)
                    {
                        highScore = 10000;
                    }
                    finally
                    {
                        if (reader != null)
                            reader.Close();
                    }
                }
            }
        }
    }
}
#endregion

20. 在 GameplayScreen 類別裡建立一個 Start的方法來啟動一個新的遊戲:

(程式碼片段 – 用 XNA 進行遊戲開發 – Gameplay Screen – Start 方法)

C#

/// <summary>
/// Starts a new game session, setting all game states to initial values.
/// </summary>
void Start()
{
    if (gameOver)
    {
        player.Score = 0;
        player.Lives = 3;
        player.RespawnTimer = 0.0f;

        gameOver = false;

        aliens.Clear();
        alienBullets.Clear();
        playerBullets.Clear();

        Respawn(0.0f);
    }

    transitionRate = 0.0f;
    transitionFactor = 0.0f;
    levelKillCount = 5;
    baseLevelKillCount = 5;
    alienScore = 25;
    alienSpawnRate = 1.0f;

    alienMaxAccuracy = 0.25f;

    alienSpeedMin = 24.0f;
    alienSpeedMax = 32.0f;

    alienSpawnRate = 2.0f;
    alienSpawnTimer = alienSpawnRate;

    nextLife = 5000;
}

21. 打開 ParticleSystem.cs 文件。

22. 添加以下宣告:

(程式碼片段 – 用 XNA 進行遊戲開發 – ParticleSystem –使用的宣告)

C#

using AlienGame;

23. 在這步中,你將添加兩個方法來建立玩家坦克移動時的泥 / 灰塵效果和玩家發射子彈的開火效果。在 ParticleSystem 類別裡添加如下方法:

(程式碼片段 – 用 XNA 進行遊戲開發 – ParticleSystem –玩家效果的輔助方法)

C#

/// <summary>
/// Creates the mud/dust effect when the player moves.
/// </summary>
/// <param name="position">Where on the screen to create the effect.</param>        
public void CreatePlayerDust(Player player)
{
    for (int i = 0; i < 2; ++i)
    {
        Particle p = CreateParticle();
        p.Texture = smoke;
        p.Color = new Color(125, 108, 43);
        p.Position.X = player.Position.X + player.Width * (float)random.NextDouble();
        p.Position.Y = player.Position.Y + player.Height - 3.0f * (float)random.NextDouble();
        p.Alpha = 1.0f;
        p.AlphaRate = -2.0f;
        p.Life = 0.5f;
        p.Rotation = 0.0f;
        p.RotationRate = -2.0f + 4.0f * (float)random.NextDouble();
        p.Scale = 0.25f;
        p.ScaleRate = 0.5f;
        p.Velocity.X = -4 + 8.0f * (float)random.NextDouble();
        p.Velocity.Y = -8 + 4.0f * (float)random.NextDouble();
    }
}

/// <summary>
/// Creates the effect for when the player fires a bullet.
/// </summary>
/// <param name="position">Where on the screen to create the effect.</param>        
public void CreatePlayerFireSmoke(Player player)
{
    for (int i = 0; i < 8; ++i)
    {
        Particle p = CreateParticle();
        p.Texture = smoke;
        p.Color = Color.White;
        p.Position.X = player.Position.X + player.Width / 2;
        p.Position.Y = player.Position.Y;
        p.Alpha = 1.0f;
        p.AlphaRate = -1.0f;
        p.Life = 1.0f;
        p.Rotation = 0.0f;
        p.RotationRate = -2.0f + 4.0f * (float)random.NextDouble();
        p.Scale = 0.25f;
        p.ScaleRate = 0.25f;
        p.Velocity.X = -4 + 8.0f * (float)random.NextDouble();
        p.Velocity.Y = -16.0f + -32.0f * (float)random.NextDouble();
    }
}

24. 編譯並執行應用程式。選擇“START GAME” 功能表項目,盡情遊戲吧。

**說明:**在模擬器中用電腦鍵盤來移動坦克,首先你需要按下 PAUSE / BREAK 鍵。這會在模擬器軟體輸入面板 (SIP) 和電腦鍵盤之間進行切換,因為它們不能同時啟動。這是一個已知問題,將會在以後的版本中修復。


圖例 28
完成遊戲

本次動手實驗到此結束。

在這個任務中,你建立了 AlienGame 的邏輯包括玩家和外星人的移動計算,擊中檢測,螢幕繪製和其他邏輯。

**說明:**這次練習的完整解決方案位於
Source \ Ex1-AlienGame \ End 資料夾下


總結

本次動手實驗向你介紹了使用 XNA 框架為 Windows Phone 平台開發應用程式。從本次動手實驗中,你為 Windows Phone 建立了一個 XNA Game Studio 遊戲,載入了遊戲資源,處理了輸入,更新遊戲狀態並添加了遊戲的具體邏輯。

透過完成本次動手實驗,你也熟悉了建立和測試一個 Windows Phone XNA Game Studio 遊戲所必需的工具。在本次實驗中,你用 Microsoft Visual Phone Developer 2010 Express 為 Windows Phone 應用建立了一個新的 XNA Game Studio 遊戲,然後使用這個免費的工具建立了應用程式的邏輯和使用者介面設計。