共用方式為


XNA 實驗室

墓碑機制,啟動器和選擇器以及更多 XNA 框架的內容

 

概觀

這次實驗基於使用XNA 進行 2D 遊戲開發的實驗,主要專注於更高級的功能,比如選擇背景音樂,發送消息(使用 SMS 任務),並在 XNA 遊戲中處理墓碑機制。雖然第一部分不是必須的,但是我們還是強烈建議你閱讀一遍。


實驗目標

本次上機實驗,你將會學習到:

  • 在設備上顯示音樂資源清單,讓玩家選擇遊戲中的背景音樂。
  • 使用墓碑機制儲存和恢復遊戲狀態。
  • 在遊戲裡執行任務和選擇器。

前置條件

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

  • 用於 Windows Phone 的 Microsoft Visual Studio 2010 Express 和帶 Windows Phone 7™ 外掛程式的 Visual Studio 2010。

安裝程式碼片段

為了方便起見,本次實驗所使用的大部分程式碼都整理為 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. 播放背景音樂
  2. 儲存和恢復遊戲狀態
  3. 從遊戲裡執行其他應用程式

本次實驗完成需要: 60分鐘。


練習 1: 播放背景音樂

在本次練習中,我們將給遊戲添加背景音樂。

這個功能允許玩家 (我們的用戶) 做到以下幾點:

  • 從手機裡的音樂資源中選一首歌,在不影響遊戲回應的情況下立即把它作為遊戲背景音樂播放。
  • 在遊戲的任何階段,選擇一首不同的歌並繼續遊戲。

要實現這個功能,我們將用到:

  • 使用 XNA 多媒體框架(Multimedia Framework)類別 MediaSourceMediaLibrary 來得到音樂資源的清單。
  • 使用 XNA 多媒體框架類別 MediaPlayer 來播放歌曲。
  • 使用實驗的第一部分我們建立的功能表系統向玩家提供一個從歌曲清單選歌的功能表。

這個練習包括兩個任務:

  • 在一個單獨的類別裡實現這個功能。
  • 在遊戲中使用這個功能類別。

任務 1 – 實現功能類

使用XNA進行2D遊戲開發的實驗 引入了 Game State Manager  ,作為控制遊戲不同功能表(畫面)流動的方法。每個畫面代表一個功能表項目或遊戲的一個邏輯部分。例如,第一個畫面是主功能表,下一個畫面是遊戲部分等。

在本任務中,你將添加一個新的畫面並建立一個名為 MusicSelectionScreen 的新類別,把功能表和多媒體功能捆綁在一起。在下一個任務中,你將從遊戲的 UI 裡使用這個類別。

1. 打開遊戲裡實驗資料夾下的Source\Ex1-BackgroundMusic (你的開發環境),這裡是使用XNA 進行 2D 遊戲開發實驗中的完整的投石車遊戲。

2. 在 Screens 資料夾裡,建立一個名為 MusicSelectionScreen 的新類別。

3. 在 MusicSelectionScreen.cs 的檔裡,用下面的程式碼覆蓋使用的宣告:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MusicSelectionScreen Usings*)

C#

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using GameStateManagement;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Media;
#endregion

4. 根據我們的慣例,按照下面的程式碼修改命名空間:

C#

namespace CatapultGame;

5. 用下面的程式碼取代類別定義:

C#

class MusicSelectionScreen : MenuScreen
{
}

**注釋:**請注意,這個類別繼承了 MenuScreen 類別,作為 Game State Manager 的一部分,它將實現功能表的功能。

6. 在類別中添加如下欄位區域:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MusicSelectionScreen Fields*)

C#

#region Fields
IList<MediaSource> mediaSourcesList;
MediaLibrary mediaLibrary;
GameScreen backgroundScreen;
#endregion

注釋: 注意,反白顯示的欄位儲存了多媒體瀏覽物件。多媒體類別庫使你可以訪問設備上的多媒體資源 (比如:歌曲列表)。

7. 在類別中添加如下初始化區域:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MusicSelectionScreen Initialization*)

C#

#region Initialization
public MusicSelectionScreen(GameScreen backgroundScreen)
    : base("Main")
{
    IsPopup = true;

    this.backgroundScreen = backgroundScreen;

    // Get the default media source
    mediaSourcesList = MediaSource.GetAvailableMediaSources();

    // Use only first one
    mediaLibrary = new MediaLibrary(mediaSourcesList[0]);

    // Create maximum 5 entries with music from music collection
    for (int i = 0; i < mediaLibrary.Songs.Count; i++)
    {
        if (i == 5)
            break;

        Song song = mediaLibrary.Songs[i];

        // Create menu entry for the song.
        MenuEntry songMenuEntry = new MenuEntry(song.Name);
        // Hook up menu event handler
        songMenuEntry.Selected += OnSongSelected;
        // Add song to the menu
        MenuEntries.Add(songMenuEntry);
    }

    // Create our menu entries.
    MenuEntry cancelMenuEntry = new MenuEntry("Cancel");

    // Hook up menu event handlers.
    cancelMenuEntry.Selected += OnCancel;

    // Add entries to the menu.
    MenuEntries.Add(cancelMenuEntry);
}
#endregion

注釋: 還要注意的是,我們初始化了多媒體物件 MediaSourcesList 和 MediaLibrary 。我們將用它們獲得可用的歌曲。MediaLibrary 物件透過它的 Songs 屬性提供了歌曲。在這個範例中,我們只是選取前 5 首歌曲,但是你可以很容易地擴充此功能。而且,要確保檢查 MediaLibrary 類別的初始屬性,因為它可以使你獲取藝術歌曲。 例如:專輯圖片。

8. 在類別中添加"Event Handlers"Add the "Event Handlers" 區域:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MusicSelectionScreen Event Handlers*)

C#

#region Event Handlers for Menu Items
/// <summary>
/// Handles Song selection and exits the menu
/// </summary>
private void OnSongSelected(object sender, EventArgs e)
{
    var selection = from song in mediaLibrary.Songs
                    where song.Name == (sender as MenuEntry).Text
                    select song;

    Song selectedSong = selection.FirstOrDefault();

    if (null != selectedSong)
        MediaPlayer.Play(selectedSong);

    backgroundScreen.ExitScreen();
    ExitScreen();
}

/// <summary>
/// Handles "Exit" menu item selection
/// </summary>
protected override void OnCancel(Microsoft.Xna.Framework.PlayerIndex playerIndex)
{
    backgroundScreen.ExitScreen();
    ExitScreen();
}
#endregion

**注釋:**OnSongSelected 功能處理歌曲選擇事件。它根據已經添加的功能表選項中的歌曲,從多媒體庫中讀取正確的歌曲物件,並使用 MediaPlayer 類別播放它。

這個任務就完成了。 MusicSelectionScreen 類包括了選擇和播放歌曲所需的所有的功能。在接下來的任務中,我們將把這個類別嵌入到遊戲中並測試它的功能。


任務 2 – 在遊戲中使用 MusicSelectionScreen 類別

我們透過在遊戲功能表中選擇相應的選項來使用這個新功能。要做到這一點,我們需要修改已有的遊戲主功能表類別。記住,我們正在擴充現有的遊戲。

1. 打開 MainMenuScreen.cs 文件。

2. 在宣告語句部分添加如下語句:

C#

using Microsoft.Xna.Framework.Media;

**注釋:**這個語句允許我們使用 MediaPlayer。和我們將要看到的一樣,當退出應用程式時,我們需要在這個類別裡使用 MediaPlayer 來停止音樂。

3. 在 MainMenuScreen  建構函式中添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MainMenuScreen Select Background Music Menu*)

C#

MenuEntry selectBackgroundMusic = new MenuEntry("Select Background Music");
selectBackgroundMusic.Selected += SelectBackgroundMusicMenuEntrySelected;
MenuEntries.Add(selectBackgroundMusic);

注釋: 此步驟是在遊戲功能表中添加一個 "Select Background Music",並分配一個事件處理器來管理它的選擇事件。

4. 在 "Event Handlers for Menu Items" 區域裡添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MainMenuScreen SelectBackgroundMusicMenuEntrySelected*)

C#

/// Handles "Select Background Music" menu item selection
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void SelectBackgroundMusicMenuEntrySelected(object sender, EventArgs e)
{
    var backgroundScreen = new BackgroundScreen();
    ScreenManager.AddScreen(backgroundScreen, null);
    ScreenManager.AddScreen(new MusicSelectionScreen(ScreenManager.Game, backgroundScreen), null);
}

**注釋:**此步驟實現了 "Select Background Music" 功能表選項處理器。它使用了由ScreenManager 物件代表的 Game State Manager ,並添加了一個由 Screen 類別代表的新畫面。在我們的案例中,這個類別是 MusicSelectionScreen 。

5. 在 "Event Handlers for Menu Items" 區域裡,用一下程式碼替換 OnCancel 事件處理器:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MainMenuScreen OnCancel*)

C#

/// <summary>
/// Handles "Exit" menu item selection
/// </summary>
/// 
protected override void OnCancel(PlayerIndex playerIndex)
{
    if (MediaPlayer.State == MediaState.Playing)
        MediaPlayer.Stop();
    ScreenManager.Game.Exit();
}

注釋: 在這步驟中,我們在 OnCancel 事件管理器中添加了反白顯示的兩行,用於當玩家登出遊戲時停止背景音樂。

6. 編譯並執行遊戲。你應該在模擬器中看到如下畫面。這是遊戲的第一個畫面,顯示了遊戲的主功能表。注意,它現在包含了一個新的功能表項目-“Select Background Music”:


圖例 1
帶有新功能表項目的主功能表

7. 選擇 "Select Background Music" 選項從以前執行的任務中打開 MusicSelectionScreen 。下面的圖例顯示了這個畫面;請注意歌曲列表。


圖例 2
背景音樂選擇畫面

當我們選擇一首歌時,畫面關閉,然後我們轉到遊戲的主功能表中。你應該聽到背景音樂就是選擇的歌曲。

恭喜你!遊戲現在正在播放背景音樂。


練習 2: 保存和恢復遊戲狀態

在這個練習中,我們給遊戲添加一個新功能,狀態持久化(Persisting State)。這個功能對處理Windows Phone 墓碑機制很有用。

Windows Phone 應用的執行模型(Execution Model)允許同一時間只能有一個應用程式執行在電話的前台上。主要的原因是最大化回應性能和電池續航時間。因此,當用戶從遊戲切換到其他應用或只是按下 Windows 按鈕時,遊戲會被停止(本質上的終止)。當用戶返回遊戲時,你應該恢復遊戲停止之前的狀態。你將使用電話的**隔離儲存區(Isolated Storage)**在叫用之間持久化遊戲的狀態資料。

注釋: 值得注意的是,應用程式不會共用在隔離儲存區中儲存的資料。換句話說,每個應用程式都有自己的隔離儲存區。 

Windows Phone 為你提供了一致的方式來處理應用程式的不同生命週期裡的事件。你將在不同的遊戲啟動和結束時,處理那些事件來完成一致持久化功能。

現在,讓我們指定 Windows Phone 應用程式的生命週期事件並顯示它們和持久化的相關性:

  • Launching(啟動): 當玩家第一次執行遊戲或關閉遊戲後再執行遊戲時觸發這個事件。在這個事件裡,我們不能使用墓碑機制 來獲取以前應用程式的狀態。但是,如果在以前的遊戲會話中,我們把遊戲狀態保存在隔離儲存區中 ,我們就能再次載入它。
  • Closing(關閉): 當玩家關閉遊戲時觸發該事件。玩家可以透過在主功能表中選擇 Exit 選項(叫用 Game.Exit方法)可以關閉遊戲。如果玩家在遊戲的第一個畫面之外按下 Back  按鈕,也可以關閉遊戲。
  •  Activated(活化): 當玩家從其他應用程式中返回遊戲會觸發該事件,例如,透過 Back 按鈕。如果你以前採用同樣技術手段保存狀態的話,在這個事件中我們可以使用墓碑機制來獲取以前應用的狀態。
  • Deactivated(暫止): 當玩家執行其他應用程式時觸發該事件,例如,使用 Start  按鈕。這時就需要透過墓碑機制來儲存狀態了。

值得注意的是在隔離儲存區 裡儲存資料要比墓碑機制安全,因為失效應用可能再也不會被啟動,這樣的話資料將會丟失。這是可能發生的情況,例如,如果我們在遊戲裡叫用了太多後續應用程式的話,就會產生資料遺失的問題,因為墓碑機制儲存是有限的。因此,你應該把所有長期資料保存到隔離儲存區中 ,把短暫資料保存到電話狀態字典裡。

現在我們開始實現程式碼來處理遊戲生命週期事件。

任務 1 –保存和恢復遊戲狀態

在這個任務中,我們用事件處理器擴展 CatapultGame 類,這樣它就可以使用墓碑機制隔離儲存區來保存和恢復應用程式的狀態,並且可以把那些處理器分配給 Windows Phone 觸發的事件:

1. 打開 CatapultGame.cs 文件。

2. 用下面的程式碼替換 "Using Statements" 區域:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*–  CatapultGame New Usings*)

C#

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using CatapultGame;
using GameStateManagement;
using Microsoft.Phone.Shell;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using System.IO;
#endregion

3. 找到 "Fields"  區域並添加如下欄位:

C#

string fileName = "Catapult.dat";

注釋: 這個檔案保存了遊戲狀態檔的名字。之後,我們會看到如何在隔離儲存區 裡建立該檔。

4. 在建構函數裡添加如下程式:

C#

InitializePhoneServices();

注釋: 這個功能使處理器捕捉住我們以前描述的應用程式的生命週期事件。

5. 找到 "Initialization Methods" 區域並添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame InitializePhoneServices*)

C#

private void InitializePhoneServices()
{
    PhoneApplicationService.Current.Activated += new EventHandler<ActivatedEventArgs>(GameActivated);
    PhoneApplicationService.Current.Deactivated += new EventHandler<DeactivatedEventArgs>(GameDeactivated);
    PhoneApplicationService.Current.Closing += new EventHandler<ClosingEventArgs>(GameClosing);
    PhoneApplicationService.Current.Launching += new EventHandler<LaunchingEventArgs>(GameLaunching);
}

注釋: 這個功能把狀態保存和恢復處理器分配給了合適的事件,這些事件由應用程式層次的 PhoneApplicationService.Current 對象代表。 Windows Phone 提供這個物件給它的應用程式並在合適條件下觸發事件。

6. 在類別中添加如下程式碼片段來定義事件處理常式:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame Event Handlers*)

C#

#region Event Handlers
/// <summary>
/// Occurs when the game class (and application) launched
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void GameLaunching(object sender, LaunchingEventArgs e)
{
    ReloadLastGameState(false);
}

/// <summary>
/// Occurs when the game class (and application) exiting during exit cycle
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void GameClosing(object sender, ClosingEventArgs e)
{
    SaveActiveGameState(false);
}

/// <summary>
/// Occurs when the game class (and application) deactivated and tombstoned
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void GameDeactivated(object sender, DeactivatedEventArgs e)
{
    SaveActiveGameState(true);
}

/// <summary>
/// Occurs when the game class (and application) activated during return from tombstoned state
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void GameActivated(object sender, ActivatedEventArgs e)
{
    ReloadLastGameState(true);
}
#endregion

注釋:在這個事件處理器區域中,我們使用 SaveActiveGameState 方法來保存應用程式的狀態,用 ReloadLastGameState 方法恢復應用程式的狀態。我們向這些方法中傳入一個BOOL(boolean)參數來指明使用哪種功能:如果參數為 true,我們釋放並重新啟動墓碑機制

7. 你需要把遊戲相關的參數保存到本機存放區或電話狀態字典中,比如,兩個玩家的遊戲分數和該哪個玩家的回合了。為了做到這一點,在 CatapultGame.cs  類別添加如下程式碼片段,這樣我們可以從事件處理器中叫用實現的功能:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame Private Functionality*)

C#

#region Private functionality
/// <summary>
/// Reload last active game state from Isolated Storage or State object
/// </summary>
/// <param name="isTombstoning"></param>
private void ReloadLastGameState(bool isTombstoning)
{
    int playerScore = 0;
    int computerScore = 0;
    bool isHumanTurn = false;
    bool isLoaded = false;

    if (isTombstoning)
        isLoaded = LoadFromStateObject(out playerScore, out computerScore, out isHumanTurn);
    else
    {
        isLoaded = LoadFromIsolatedStorage(ref playerScore, ref computerScore, ref isHumanTurn);

        if (isLoaded)
        {
            if (System.Windows.MessageBox.Show("Old game available.\nDo you want to continue last game?", "Load Game", System.Windows.MessageBoxButton.OKCancel) == System.Windows.MessageBoxResult.OK)
                isLoaded |= true;
            else
                isLoaded = false;
        }
    }

    if (isLoaded)
    {
        PhoneApplicationService.Current.State.Add("playerScore", playerScore);
        PhoneApplicationService.Current.State.Add("computerScore", computerScore);
        PhoneApplicationService.Current.State.Add("isHumanTurn", isHumanTurn);

        GameplayScreen gameplayScreen = new GameplayScreen();
        mainMenuScreen.ScreenManager.AddScreen(gameplayScreen, null);
    }
}

private bool LoadFromIsolatedStorage(ref int playerScore, ref int computerScore, ref bool isHumanTurn)
{
    bool res = true; // Assume success

    // Load from Isolated Storage file
    using (IsolatedStorageFile isolatedStorageFile
        = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (isolatedStorageFile.FileExists(fileName))
        {
            //If user choose to save, create a new file
            using (IsolatedStorageFileStream fileStream
                = isolatedStorageFile.OpenFile(fileName, FileMode.Open))
            {
                using (StreamReader streamReader = new StreamReader(fileStream))
                {
                    playerScore = int.Parse(streamReader.ReadLine(),
                                            System.Globalization.NumberStyles.Integer);
                    computerScore = int.Parse(streamReader.ReadLine(),
                                                System.Globalization.NumberStyles.Integer);
                    isHumanTurn = bool.Parse(streamReader.ReadLine());
                    streamReader.Close();
                }
            }

            res = true;
            isolatedStorageFile.DeleteFile(fileName);
        }
        else
            res = false;
    }

    return res;
}

private bool LoadFromStateObject(out int playerScore, out int computerScore, out bool isHumanTurn)
{
    playerScore = -1;
    computerScore = -1;
    isHumanTurn = false;
    bool res = true; // Assume success

    if (PhoneApplicationService.Current.State.ContainsKey("HumanScore"))
    {
        playerScore = (int)PhoneApplicationService.Current.State["HumanScore"];
        PhoneApplicationService.Current.State.Remove("HumanScore");
    }
    else
        res = false;

    if (PhoneApplicationService.Current.State.ContainsKey("PhoneScore"))
    {
        computerScore = (int)PhoneApplicationService.Current.State["PhoneScore"];
        PhoneApplicationService.Current.State.Remove("PhoneScore");
    }
    else res = false;

    if (PhoneApplicationService.Current.State.ContainsKey("isHumanTurn"))
    {
        isHumanTurn = (bool)PhoneApplicationService.Current.State["isHumanTurn"];
        PhoneApplicationService.Current.State.Remove("isHumanTurn");
    }
    else
        res = false;

    return res;
}

/// <summary>
/// Saves the current game state (if game is running) to Isolated Storage or State object
/// </summary>
/// <param name="isTombstoning"></param>
private void SaveActiveGameState(bool isTombstoning)
{
    // Try finding the running game instance 
    var res = from screen in screenManager.GetScreens()
                where screen.GetType() == typeof(GameplayScreen)
                select screen;

    GameplayScreen gameplayScreen = res.FirstOrDefault() as GameplayScreen;

    if (null != gameplayScreen)
    {
        if (isTombstoning)
        {
            SaveToStateObject(gameplayScreen);
        }
        else
        {
            SaveToIsolatedStorageFile(gameplayScreen, fileName);
        }
    }
}

/// <summary>
/// Saves the gameplay screen data to Isolated storage file
/// </summary>
/// <param name="gameplayScreen"></param>
/// <param name="fileName"></param>
private void SaveToIsolatedStorageFile(GameplayScreen gameplayScreen, string fileName)
{
    // Save to Isolated Storage file
    using (IsolatedStorageFile isolatedStorageFile
        = IsolatedStorageFile.GetUserStoreForApplication())
    {
        // If user choose to save, create a new file
        using (IsolatedStorageFileStream fileStream
            = isolatedStorageFile.CreateFile(fileName))
        {
            using (StreamWriter streamWriter = new StreamWriter(fileStream))
            {
                // Write date to the file
                streamWriter.WriteLine(gameplayScreen.player.Score);
                streamWriter.WriteLine(gameplayScreen.computer.Score);
                streamWriter.WriteLine(gameplayScreen.isHumanTurn);
                streamWriter.Close();
            }
        }
    }
}

/// <summary>
/// Saves the gameplay screen data to State Object
/// </summary>
/// <param name="gameplayScreen"></param>
private static void SaveToStateObject(GameplayScreen gameplayScreen)
{
    // Save date to the State object
    PhoneApplicationService.Current.State.Add("HumanScore", gameplayScreen.player.Score);
    PhoneApplicationService.Current.State.Add("PhoneScore", gameplayScreen.computer.Score);
    PhoneApplicationService.Current.State.Add("isHumanTurn", gameplayScreen.isHumanTurn);
}
#endregion

8. 在 “Screens” 資料夾下打開 GameplayScreen.cs 文件。

9. 在 "Using Statements" 區域添加如下程式:

C#

using Microsoft.Phone.Shell;

注釋: 這個語句允許使用我們以前提到的PhoneApplicationService。

10. 找到 LoadAssets 方法並用下面的程式碼替換:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– GameplayScreen LoadAssets*)

C#

public override void LoadAssets()
{
    // Load textures
    foregroundTexture = Load<Texture2D>("Textures/Backgrounds/gameplay_screen");
    cloud1Texture = Load<Texture2D>("Textures/Backgrounds/cloud1");
    cloud2Texture = Load<Texture2D>("Textures/Backgrounds/cloud2");
    mountainTexture = Load<Texture2D>("Textures/Backgrounds/mountain");
    skyTexture = Load<Texture2D>("Textures/Backgrounds/sky");
    defeatTexture = Load<Texture2D>("Textures/Backgrounds/defeat");
    victoryTexture = Load<Texture2D>("Textures/Backgrounds/victory");
    hudBackgroundTexture = Load<Texture2D>("Textures/HUD/hudBackground");
    windArrowTexture = Load<Texture2D>("Textures/HUD/windArrow");
    ammoTypeTexture = Load<Texture2D>("Textures/HUD/ammoType");
    // Load font
    hudFont = Load<SpriteFont>("Fonts/HUDFont");

    // Define initial cloud position
    cloud1Position = new Vector2(224 - cloud1Texture.Width, 32);
    cloud2Position = new Vector2(64, 90);

    // Define initial HUD positions
    playerHUDPosition = new Vector2(7, 7);
    computerHUDPosition = new Vector2(613, 7);
    windArrowPosition = new Vector2(345, 46);

    // Initialize human & AI players
    player = new Human(ScreenManager.Game, ScreenManager.SpriteBatch);
    player.Initialize();
    player.Name = "Player";

    computer = new AI(ScreenManager.Game, ScreenManager.SpriteBatch);
    computer.Initialize();
    computer.Name = "Phone";

    // Identify enemies
    player.Enemy = computer;
    computer.Enemy = player;

    base.LoadContent();

    if (PhoneApplicationService.Current.StartupMode ==
 StartupMode.Activate)
    {
        player.Score = int.Parse(PhoneApplicationService.Current.State["playerScore"].ToString());
        computer.Score = int.Parse(PhoneApplicationService.Current.State["computerScore"].ToString());
        isHumanTurn = !bool.Parse(PhoneApplicationService.Current.State["isHumanTurn"].ToString());

        wind = Vector2.Zero;
        changeTurn = true;
        computer.Catapult.CurrentState = CatapultState.Reset;
    }
    else
        // Start the game
        Start();
}

注釋: 注意在方法後面 if 結構裡的語句。這些語句使用墓碑機制來重新載入應用程式的狀態,第一個反白語句會檢查是否有可用的狀態。

注意方法裡的邏輯被 PhoneApplicationService  類提供的 "StartupMode" 標誌位元觸發。這個標誌位元表示應用程式是退出後再啟動的還是僅僅是重新啟動。

11. 找到 LoadContent 方法並用下面程式碼替換:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– GameplayScreen LoadAssets*)

C#

public override void LoadContent()
{
    base.LoadContent();
    LoadAssets();
}

現在我們的遊戲具備了持久化的特性。你可以透過執行遊戲然後按下 Windows 按鈕來測試它。這就把遊戲墓碑化了,你可以按下 Back 按鈕來重新啟動它。

這個任務結束了。使用具有持久化功能的遊戲,你可以轉到短信系統來告訴你的朋友這個遊戲有多好玩了。


練習 3: 從遊戲中執行其他應用程式

在這個練習中,我們給遊戲添加一個新功能,即執行其他應用程式的能力。

正如我們所說, Windows Phone 只允許一次執行一個應用程式。同時,你既不能執行其他應用程式,也不能訪問電話資訊或應用,比如連絡人列表。你需要一個特殊的方法從執行的遊戲中執行其他應用程式。 Choosers(選擇器)和 Launchers(啟動器) 可以讓你用電話特定的應用瀏覽特定的資料或者使用電話的功能,比如照相機。想學習關於 choosers  和 launchers 的更多內容,請參考本系列培訓中的 Launchers and Choosers 實驗手冊。

請注意,當別的應用程式正在執行時,你的應用程式是不能執行的。而且,電話應該知道你不是退出我們的應用而是切換到其他應用,有可能還會切換回來。我們將在最後的練習中處理墓碑的狀態 。我們這樣做是正確的,因為當你執行特定的 choosers 和launchers ,你的應用將被墓碑化。

任務 1 –從遊戲中執行其他應用

我們已經提過,我們使用 ChoosersLaunchers 從我們的遊戲中執行其他應用。 ChoosersLaunchers 的不同之處在於 Choosers 向遊戲傳回結果,而 Launchers 不會。開發平台提供了一個豐富的 ChoosersLaunchers 的集合可以直接執行標準的 Windows Phone 任務。在這個練習中,我們將使用 PhoneNumberChooserTask 類型的 ChoosersSmsComposeTask 類型的 Launcher

PhoneNumberChooserTask 執行標準的電話簿應用並且將選擇的號碼傳回給遊戲。SmsComposeTask 向這個號碼發送簡訊。在這個任務中,你將把這兩個功能整合到遊戲中。

1. 打開 CatapultGame.cs 文件。

2. 在 "Using Statements" 區域添加如下程式:

C#

using Microsoft.Phone.Tasks;

**注釋:**這行程式允許我們使用 ChoosersLaunchers

3. 在 "Fields" 區域中添加如下欄位:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame Fields*)

C#

PhoneNumberChooserTask phoneNumberChooserTask;
SmsComposeTask smsComposeTask;

注釋: 我們將保存 Choose 和 I Launcher 物件保存到那些欄位中。

4. 找到 CatapultGame 的建構函數並用下面的程式碼替換:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame Constructor*)

C#

public CatapultGame()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    // Frame rate is 30 fps by default for Windows Phone.
    TargetElapsedTime = TimeSpan.FromTicks(333333);

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

    //Switch to full screen for best game experience
    graphics.IsFullScreen = true;

    //Add two new screens
    screenManager.AddScreen(new BackgroundScreen(), null);
    screenManager.AddScreen(new MainMenuScreen(), null);

    //Create Chooser and Launcher
    InitializeChooserAndLauncher();

    AudioManager.Initialize(this);
    InitializePhoneServices();
}

注釋: 注意反白顯示的程式。它初始化了 Chooser 和 Launcher 的物件。

5. 在 "Initialization Methods" 區域裡添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame InitializeChooserAndLauncher*)

C#

private void InitializeChooserAndLauncher()
{
    phoneNumberChooserTask = new PhoneNumberChooserTask();
    smsComposeTask = new SmsComposeTask();
    phoneNumberChooserTask.Completed += new EventHandler<PhoneNumberResult>(PhoneNumberChooserTaskCompleted);
}

注釋: 本程式將會被初始化需要的 Choosers 與 Launchers

6. 在 "Event Handlers" 區域中添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame Event Handlers*)

C#

/// <summary>
/// Occurs when the Phone Number Chooser task completes
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
void PhoneNumberChooserTaskCompleted(object sender, PhoneNumberResult args)
{
    // If user provided the requested info
    if (args.TaskResult == TaskResult.OK)
    {
        // Create, initialize and show SMS composer launcher
        smsComposeTask.To = args.PhoneNumber;
        smsComposeTask.Body =
            "Hello! Just discovered very good game called Catapult Wars. Try it by yourself and see!";
        smsComposeTask.Show();
    }
}

注釋: 這個事件處理器透過向選定的號碼發送簡訊,完成 "Phone Number Chooser" 任務。

7. 在 CatapultGame 類別中添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– CatapultGame ExecuteTask*)

C#

#region Public Interface
public void ExecuteTask()
{
    phoneNumberChooserTask.Show();
}
#endregion

**注釋:**這個程式碼允許從功能表裡叫用我們的 Chooser 和 Launcher 。

8. 打開 “Screens” 資料夾下的 MainMenuScreen.cs 文件。

9. 找到建構函數,並用下面的程式碼替換:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MainMenuScreen Constructor*)

C#

public MainMenuScreen()
    : base(String.Empty)
{
    IsPopup = true;

    // Create our menu entries.
    MenuEntry startGameMenuEntry = new MenuEntry("Play");
    MenuEntry selectBackgroundMusic = new MenuEntry("Select Background Music");
    MenuEntry shareMenuEntry = new MenuEntry("Tell a Friend");
    MenuEntry exitMenuEntry = new MenuEntry("Exit");

    // Hook up menu event handlers.
    startGameMenuEntry.Selected += StartGameMenuEntrySelected;
    selectBackgroundMusic.Selected += SelectBackgroundMusicMenuEntrySelected;
    shareMenuEntry.Selected += ShareMenuEntrySelected;

    exitMenuEntry.Selected += OnCancel;

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

注釋: 注意反白顯示的程式。那些程式引入了一個新的功能表項目,即執行電話簿並向選定的號碼發送簡訊。

10. 找到“Event Handlers for Menu Items” 並添加如下程式碼:

(程式碼片段–使用 XNA 進行 2D 遊戲開發第二部分*– MainMenuScreen ShareMenuEntrySelected*)

C#

/// <summary>
/// Handler "Share" menu item selection
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ShareMenuEntrySelected(object sender, EventArgs e)
{
    //Execute a task defined in the game class
    ((CatapultGame)(ScreenManager.Game)).ExecuteTask();
}

注釋: 這就是新功能表項目的事件處理器。這個處理器執行之前提到的遊戲應用程式中定義的任務。這個處理器執行電話簿應用程式,獲取選中的號碼,並發送簡訊。

11. 這個任務和練習就完成了。

12. 現在我們的遊戲玩家可以透過簡訊和朋友分享分數了。


總結

在本次實驗中,我們透過增加背景音樂的新功能,持久化和瀏覽其他應用程式來擴充遊戲。我們還透過新技術,比如墓碑機制,隔離儲存區,Launchers 和 Choosers 擴充了遊戲開發的技巧。