Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト
Visual Studio 2005
MSDN Home > Visual Studio > Visual Studio 2008 Express Edition > 学習情報 > XNA > 第 05 回
ホーム Web インストール 製品の特徴 製品の機能 サポート 前のバージョン
XNA で作る、あなただけのマインスイーパ

XNA Framework 第 05 回

赤坂玲音

システムのサービスを使う

XNA Game Studio 2.0 では、Microsoft のゲームプラットフォームが提供する基本的なサービスを利用できるようになりました。サインインしているゲーマーの情報など、Xbox LIVE や Games for Windows といったサービスにアクセスできるほか、キーボード入力画面の表示や、システムメッセージの表示などが可能となります。こうした機能は、基本的に Xbox 360 のシステムで提供されているものでしたが、Windows 上でも XNA Framework が Xbox 360 と同じような機能を再現してくれます。

システムサービスを利用することによって、テキスト入力画面やサインインを制御する画面などを独自に作る必要がなくなります。画面の表示や入力の処理は、すべて基盤システムに委ねることができます。また、プレイヤーにとっても、異なる XNA Framework ゲームが同じ UI を使うことで操作方法に迷うことがなくなるというメリットがあります。

XNA では、こうしたシステムが提供するサービスを総称してゲーマーサービスと呼んでいます。ゲーマーサービスに依存する機能を呼び出すには、先に  Microsoft.Xna.Framework.GamerServices.GamerServicesComponent クラスのインスタンスを生成し、ゲーム部品として Components に登録しなければなりません。

■ Microsoft.Xna.Framework.GamerServices.GamerServicesComponent クラス

public class GamerServicesComponent : GameComponent

このクラスのコンストラクタには、関連付けるゲームの Game オブジェクトを指定します。

■ GamerServicesComponent クラスのコンストラクタ

public GamerServicesComponent (
         Game game
)

生成したインスタンスは、必ず Game クラスの Components プロパティからゲーム部品として追加してください。一般的には、ゲームの起動時に実行されるコンストラクタなどに次のようなコードを挿入します。

Components.Add(new GamerServicesComponent(this));

これで、ゲーマーサービスを利用する準備は整いました。XNA Framework 2.0 以降は、LIVE ネットワーク機能が使えるようになったためサインインやゲーマープロファイルなどの機能も追加されています。こうしたゲーマーサービスを利用するには、先にシステムにサインインしなければなりません。幸い、サインイン画面を独自に作る必要はなく、システムが提供してくれています。

GamerServicesComponent は、Xbox 360 のダッシュボード画面と同じような機能を提供します。アプリケーション側から明示的に特定の画面を表示させることも可能ですが、GamerServicesComponent をゲームに登録している状態であれば、Xbox 360 コントローラのガイドボタン、またはキーボードの Home ボタンを押すことでシステム画面を表示できます。

■ 図 01 ガイド

ガイド

XNA Framework では、こうしたシステムが提供する管理画面をガイドと呼んでいます。ガイドには、サインイン画面の他に、メッセージボックスやテキスト入力システムなども含まれます。この画面から、サインインやサインアウト、フレンドの確認やメッセージの送受信、音声チャットや設定など、Xbox 360 のダッシュボード内の Xbox Live ブレードと同じような機能を利用することができます。

ガイドを利用するには  Microsoft.Xna.Framework.GamerServices.Guide クラスを使います。

■ Microsoft.Xna.Framework.GamerServices.Guide クラス

public static class Guide

このクラスは、ガイドを表示するための静的なメソッドやプロパティを公開しています。Guide クラスの機能を利用するには、GamerServicesComponent がゲームに登録され、Initialize() メソッドが呼び出された後でなければなりません。初期化がおこなわれていない状態で Guide クラスのメソッドなどを呼び出すと、例外が発生します。

サインイン

Xbox 360 で XNA Framework ゲームを実行するには Creators Club のメンバシップが必要でしたが、これまでのオフラインでのゲームの場合 Windows PC 上で実行するには不要でした。しかし、LIVE サービスにサインインし、ネットワークを通じて他のプレイヤーと対戦するには、Windows PC で実行するゲームでも LIVE と Creators Club のメンバシップが必要になります。

Microsoft は、ゲーマータグでプレイヤーを識別するオンラインのコミュニティサービスを提供しています。Xbox 360 プラットフォームで Xbox LIVE と呼ばれているこのサービスは、Windows プラットフォーム上では Games for Windows - LIVE と呼ばれ、Windows プラットフォーム上では Games for Windows - LIVE と呼ばれ、Xbox LIVE アカウントをそのまま Games for Windows - LIVE でも使うことができます。

LIVE アカウントの詳細は Xbox Live のページをご覧ください。

http://www.xbox.com/ja-jp/live/

Games for Windows- LIVE の詳細は、Games for Windows のページをご覧ください。

http://www.microsoft.com/japan/games/gamesforwindows/default.mspx

Xbox 360 と Windows PC で個別にメンバシップを購入する必要はありません。すでに Creators Club メンバシップを持つ Xbox LIVE アカウントをお使いであれば、その ID で本稿のサンプルにサインインしていただくことができます。

■ 表01 接続に必要なメンバシップ

接続内容 Xbox 360 Windows PC
システムリンク LIVE Silver, Creators Club 特になし
LIVE サービスへのサインイン LIVE Silver, Creators Club LIVE Silver, Creators Club
LIVE による対戦 LIVE Gold, Creators Club LIVE Gold, Creators Club

表 01 は、LIVE サービスを利用する場合に必要なメンバシップです。LAN 上でゲームを接続するシステムリンクの場合、Windows PC 上で実行するゲームであればメンバシップは不要です。しかし、LIVE サービスを利用する場合は Windows PC でも LIVE Silver 以上のメンバシップと、Creators Club のメンバシップが必要になります。さらに、LIVE を使った通信対戦を行う場合は LIVE Gold メンバシップが必要になります。

ゲームにサインインするには、Guide クラスの ShowSingIn() メソッドを呼び出してサインイン画面を表示します。ガイドボタンを押してガイドを表示することもできますが、このメソッドを使うことでアプリケーションからサインイン画面を呼び出すことができます。

■ Guide クラス ShowSignIn() メソッド

public static void ShowSignIn (
         int paneCount,
         bool onlineOnly
)

paneCount には、サインインさせるプレイヤーの数を指定します。有効な値は 1、2、4 のいずれかです。1 を指定した場合は、1 人のプレイヤーをサインインさせる画面が表示されます。4 を指定した場合は、上下左右に画面分割された 4 人分のサインイン画面が表示されるでしょう。このパラメータは、現在では Xbox 360 のみ対応し、Windows PC の場合は機能しません。Windows PC の場合は、常に 1 人用のサインイン画面となります。

onlineOnly は、LIVE に接続可能なアカウントのみを許容するかどうかを指定します。この値が true の場合、画面には LIVE に接続可能なアカウントのみが表示され、ローカルアカウントはゲストとして表示されます。false の場合、ローカルアカウントも含めたすべてのアカウントが表示されます。

■ Sample01

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        base.Initialize();
        Guide.ShowSignIn(1, false);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);
        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample01 を実行し、初めてサイン新画面を表示した場合は上記のような結果が表示されます。このように、画面の描画やデバイスからの入力処理も、すべてシステムが代わって行ってくれます。ゲーム開発者は、ShowSignIn() メソッドを呼び出すだけで、利用者を LIVE に接続させる画面に導くことができます。Xbox LIVE アカウントをお持ちの場合は「持っているプロフィールを使う」を選択してサインインすることができます。Windows PC の XNA Framework ゲーム上でサインインするには Creators Club のメンバシップが必要になるので注意してください。

サインインする

ネットワーク対応のゲームの開発を試すためにも、まずはアカウントをサインインさせなければなりません。Sample01 を実行して、そのままサインインしてみましょう。誤ってガイドを閉じてしまっても、Xbox 360 コントローラのガイドボタンを押すか、キーボードの Home ボタンを押すことで表示できます。サインインするには、LIVE メンバシップの ID とパスワードを入力して、必要であれば ID やパスワードの保存、自動サインインなどのチェックボックスにチェックを入れて「選択」を押します。

■ 図 01 ID入力

ID入力

入力した ID が初めてのサインインの場合、プロフィールがダウンロードされます。この作業は 2 回目以降は発生しません。

■ 図 02 プロフィールのダウンロード

サインイン時に必要なコンポーネントがインストールされていない場合や、更新が必要な場合は「更新が必要です」という画面が表示されます。「更新しますか?」の質問に対して「はい、更新します」を選択すると必要なコンポーネントがダウンロードされ、ゲームが終了するので再起動してください。「いいえ」を選択した場合は、サインイン画面が閉じられます。

■ 図 03 更新の確認

必要なダウンロードやインストールは、すべて自動的に行われます。

■ 図 04 更新

必要な更新を行い、正しく LIVE アカウントでサインインすることができれば、今後のプログラムでゲーマータグを使った処理を行うことができるようになります。一度サインインしたアカウントはシステムに記録され、2 回目以降はゲーマータグを選択するだけでサインインできるようになります。こうした点は、Xbox 360 プラットフォームと共通した仕組みになっています。

■ 図 05 2回目以降のサインイン

ゲーマータグを持つプレイヤーは、Microsoft.Xna.Framework.GamerServices.Gamer クラスのインスタンスで識別することができます。サインインしているプレイヤーやフレンド、ネットワークで接続している他のプレイヤーなどは、この抽象クラスから派生したクラスのオブジェクトとなります。そのため、すべてのプレイヤーを Gamer オブジェクトとして扱うことができます。

■ Microsoft.Xna.Framework.GamerServices.Gamer クラス

public abstract class Gamer

システムにサインインしているプレイヤーの情報は、Gamer クラスの静的な SignedInGamers プロパティから取得することができます。

■ Gamer クラス SignedInGamers プロパティ

public static SignedInGamerCollection SignedInGamers { get; }

このプロパティは、サインインしているプレイヤーのコレクションを管理している Microsoft.Xna.Framework.GamerServices.SignedInGamerCollection クラスのオブジェクトを返します。

■ Microsoft.Xna.Framework.GamerServices.SignedInGamerCollection クラス

public sealed class SignedInGamerCollection : GamerCollection<SignedInGamer>

このクラスは、任意の数の Gamer オブジェクトを管理する Microsoft.Xna.Framework.GamerServices.GamerCollection クラスから派生しています。サインインしているプレイヤーだけに限らず、フレンドなど Gamer を継承するオブジェクトのコレクションを管理するクラスは、GamerCollection から派生しています。

■ Microsoft.Xna.Framework.GamerServices.GamerCollection クラス

public class GamerCollection<T> : ReadOnlyCollection<T>, IEnumerable<Gamer>, IEnumerable where T : Gamer

GamerCollection クラスは、Gamer 型に互換性のある型に制約された型パラメータ T を持つクラスです。ReadOnlyCollection から派生しているため、コレクションは読み取り専用です。SignedInGamerCollection クラスは GamerCollection<SignedInGamer> を継承しています。この型パラメータは、サインインしているプレイヤーを表す Microsoft.Xna.Framework.GamerServices.SignedInGamer クラスです。SignedInGamer クラスは Gamer クラスを継承しています。

■ Microsoft.Xna.Framework.GamerServices.SignedInGamer クラス

public sealed class SignedInGamer : Gamer

よって SignedInGamerCollection は、サインインしている SignedInGamer オブジェクトを提供します。通常のコレクションと同じように、インデクサに整数を指定して要素を取得することもできますが、サインインできるプレイヤーの数は、Xbox 360 の制約から PlayerIndex 列挙型のメンバに一致します。そこで、指定した PlayerIndex の値に対応する SignedInGamer を返すインデクサがオーバーロードされています。

■ SignedInGamerCollection クラスのインデクサ

public SignedInGamer this [
         PlayerIndex index
] { get; }

ここから、特定のプレイヤーがサインインしているかどうかを調べることができます。プレイヤーがサインインしていない場合は null が返されます。

Gamer オブジェクトを取得することができれば、Gamertag プロパティから、Gamer オブジェクトのゲーマータグを取得することができます。ゲーム画面上などでプレイヤーを識別する文字列として利用することができます。

■ Gamer クラス Gamertag プロパティ

public string Gamertag { get; }

このプロパティの結果は、ゲーマータグを表す文字列です。LIVE にサインインしているゲーマータグは一意の文字列となるため、他の Gamer オブジェクトと衝突することはありません。ゲーマータグを調べることで、プレイヤーを識別できます。

■ Sample02

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private SpriteFont font;
    private string text;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        base.Initialize();
    }

    protected override void LoadContent()
    {
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    protected override void Update(GameTime gameTime)
    {
        SignedInGamer gamer1 = Gamer.SignedInGamers[PlayerIndex.One];
        SignedInGamer gamer2 = Gamer.SignedInGamers[PlayerIndex.Two];
        SignedInGamer gamer3 = Gamer.SignedInGamers[PlayerIndex.Three];
        SignedInGamer gamer4 = Gamer.SignedInGamers[PlayerIndex.Four];

        text = "Player1=" + (gamer1 == null ? "Null" : gamer1.Gamertag) + "\n" +
                "Player2=" + (gamer2 == null ? "Null" : gamer2.Gamertag) + "\n" +
                "Player3=" + (gamer3 == null ? "Null" : gamer3.Gamertag) + "\n" +
                "Player4=" + (gamer4 == null ? "Null" : gamer4.Gamertag);

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);
        sprite.Begin();
        sprite.DrawString(font, text, Vector2.Zero, Color.Black);
        sprite.End();
        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample02 は、現在サインインしているアカウントのゲーマータグを表示します。PlayerIndex に対応するプレイヤーがサインインしている場合、適切な SignedInGamer オブジェクトが返されますが、サインインしていない場合は null が返されます。Update() メソッドは、取得した SignedInGamer を調べ null でなければ Gamertag プロパティの値を文字列に加えています。プロジェクトには、文字列を描画するために適当な SpriteFont を追加し "TestFont" というアセット名を付けてください。

ガイドが表示されている間も、ゲームの Update() メソッドや Draw() メソッドは呼び出され続けています。背景に半透明に透けて文字列が描画されていることに注目してください。Update() メソッド内などから明示的にガイドを呼び出す場合、すでにガイドが表示されていないかどうかを調べる必要があります。ガイドが表示されている状態で、ガイドを表示しようとすると例外が発生します。ガイドが表示されているかどうかは IsVisible プロパティから取得します。

■ Guide クラス IsVisible プロパティ

public static bool IsVisible { get; set; }

ガイドが表示されている場合は true、そうでなければ false を返します。入力などに反応してガイドを表示させる場合、すでにガイドが表示されていないかどうかを調べてから表示させてください。

サインインの通知

プレイヤーは、ガイドを表示して任意のタイミングでサインインまたはサインアウトすることができるため、ゲーム中などの予期しないタイミングでサインアウトされるといった不都合が発生する可能性があります。そこで、プレイヤーのサインインやサインアウトをイベントで感知して、何らかの処理を挟むことができます。サインイン、またはサインアウトされると SignedInGamers クラスの静的な SignedIn イベントと、SignedOut イベントが実行されます。

プレイヤーがサインインすると、SignedIn イベントに登録しているデリゲートが呼び出されます。

■ SignedInGamers クラス SignedIn イベント

public static event EventHandler<SignedInEventArgs> SignedIn

EventHandler の型パラメータに指定されているのは Microsoft.Xna.Framework.GamerServices.SignedInEventArgs クラスです。このクラスは、サインインしたアカウントの情報を提供します。

■ Microsoft.Xna.Framework.GamerServices.SignedInEventArgs クラス

public class SignedInEventArgs : EventArgs

サインインした Gamer オブジェクトは、Gamer プロパティから取得できます。

■ SignedInEventArgs クラス Gamer プロパティ

public SignedInGamer Gamer { get; }

このプロパティは、サインインした Gamer オブジェクトを返します。Xbox 360 で XNA Framework ゲームを起動する場合は、すでにサインインしている状態でなければなりませんが、サインインしている状態でゲームが起動された場合は、初期化処理が終了した後に、このイベントが発生します。

同様に、アカウントがサインアウトすると SignedOut イベントが発生します。

■ SignedInGamers クラス SignedOut イベント

public static event EventHandler<SignedOutEventArgs> SignedOut

仕組みはサインインと同じです。このイベントの EventHandler に指定されている型パラメータは、サインアウト情報を通知するための Microsoft.Xna.Framework.GamerServices.SignedOutEventArgs クラスです。

■ Microsoft.Xna.Framework.GamerServices.SignedOutEventArgs クラス

public class SignedOutEventArgs : EventArgs

このクラスは、サインアウトしたアカウントを Gamer プロパティに保存しています。型は異なりますが、SignedInEventArgs と同じです。

■ SignedOutEventArgs クラス Gamer プロパティ

public SignedInGamer Gamer { get; }

これで、ゲーム起動中に行われたサインインやサインアウトに対応した処理を介入させることができます。

■ Sample03

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private SpriteFont font;
    private string text;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        text = "Please sign in or sign out...\n";
        SignedInGamer.SignedIn += delegate(object sender, SignedInEventArgs e) {
            text += "SignedIn=" + e.Gamer.Gamertag + "\n";
        };
        SignedInGamer.SignedOut += delegate(object sender, SignedOutEventArgs e)
        {
            text += "SignedOut=" + e.Gamer.Gamertag + "\n";
        };
        base.Initialize();
    }

    protected override void LoadContent()
    {
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);
        sprite.Begin();
        sprite.DrawString(font, text, Vector2.Zero, Color.Black);
        sprite.End();
        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample03 は、サインイン、またはサインアウトすると text 変数にゲーマータグを追加します。Draw() メソッドで text 変数の内容を描画しています。ガイドを表示してサインイン、サインアウトすると、画面にゲーマータグが表示されることを確認してください。

ゲーマープロファイル

ゲーマータグには、プロファイルが関連付けられています。プロファイルは Microsoft の LIVE サービスによって提供されるもので、サインインしているプレイヤーのアイコン、ゲームスコア、国や言語、自由入力の行動指針を表す文字列などを取得することができます。プロファイルを取得するには、Gamer クラスの GetProfile() メソッドを使います。

■ Gamer クラス GetProfile() メソッド

public GamerProfile GetProfile ()

このメソッドは、プロファイルを表す Microsoft.Xna.Framework.GamerServices.GamerProfile クラスのオブジェクトを返します。このクラスは、プロファイルに含まれるユーザー情報を提供するプロパティを持ちます。ただし、プロファイルをゲームから設定することはできません。すべてのプロパティは読み取り専用です。

■ Microsoft.Xna.Framework.GamerServices.GamerProfile クラス

public sealed class GamerProfile : IDisposable

ゲーマータグに関連付けられているアイコンは GamerPicture プロパティから取得できます。

■ GamerProfile クラス GamerPicture プロパティ

public Texture2D GamerPicture { get; }

このプロパティは、アイコンの画像を表す Texture2D オブジェクトを返します。これを使って、ゲーム内でアイコンを描画することも可能です。

解除した実績のゲームスコアは GamerScore プロパティから取得できます。この値から、プレイヤーがどの程度ゲームをやり込んでいるかを知ることができます。

■ GamerProfile クラス GamerScore プロパティ

public int GamerScore { get; }

プレイヤーが、どのようなスタイルでゲームを遊ぶのかという情報を GamerZone プロパティから取得することができます。この情報は、ゾーンと呼ばれています。

■ GamerProfile クラス GamerZone プロパティ

public GamerZone GamerZone { get; }

このプロパティは、Microsoft.Xna.Framework.GamerServices.GamerZone 列挙型の値を返します。

■ Microsoft.Xna.Framework.GamerServices.GamerZone 列挙型

public enum GamerZone

GamerZone 列挙型には、家族向けの Family、勝利にこだわる Pro、楽しむことを大切にする Recreation、スリルを求める挑戦的な Underground、そしてゾーンが不明であることを表す Unknown メンバがあります。

プレイヤーが自由入力したテキストによる行動指針を Motto プロパティから取得できます。この情報は、モットーと呼ばれています。

■ GamerProfile クラス Motto プロパティ

public string Motto { get; }

Motto は自由入力となるため、どのような文字が使われているかはコンパイル時に把握できません。描画する場合は、含まれる文字に対応するフォントが SpriteFont に存在するかどうか、調べる必要があります。

■ Sample04

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private SpriteFont font;
    private GamerProfile profile;
    private string text;
    private Vector2 textPosition;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);

        text = "Please sign in...\n";
        textPosition = Vector2.Zero;

        SignedInGamer.SignedIn += delegate(object sender, SignedInEventArgs e)
        {
            profile = e.Gamer.GetProfile();
            text = e.Gamer.Gamertag + " " + profile.GamerScore + "\n" + 
                profile.GamerZone + "\n" +
                profile.Motto;
            textPosition = new Vector2(0, profile.GamerPicture.Height + 5);
        };
        base.Initialize();
    }

    protected override void LoadContent()
    {
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);
        sprite.Begin();
        if (profile != null)
            sprite.Draw(profile.GamerPicture, Vector2.Zero, Color.White);
        sprite.DrawString(font, text, textPosition, Color.Black);
        sprite.End();
        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample04 は、サインインしたアカウントのプロファイルを取得し、アイコン、スコア、ゾーン、そしてモットーを描画しています。モットーに日本語が含まれる場合、SpriteFont に日本語フォントを含ませる必要があります。すべての日本語フォントを含めると、ファイルサイズが肥大するので注意が必要です。

フレンド

サインインしているアカウントに登録されているフレンド情報は、GetFriends() メソッドから取得することができます。

■ SignedInGamer クラス GetFriends() メソッド

public FriendCollection GetFriends ()

このメソッドが返すのは、登録されているフレンドの配列を管理する Microsoft.Xna.Framework.GamerServices.FriendCollection クラスのオブジェクトです。SignedInGamerCollection クラスと同じように GamerCollection<FriendGamer> クラスから派生しています。

■ Microsoft.Xna.Framework.GamerServices.FriendCollection クラス

public sealed class FriendCollection : GamerCollection<FriendGamer>, IDisposable

使い方も SignedInGamerCollection クラスと同じです。Count プロパティからフレンドの数を取得し、インデクサからフレンドを表す Microsoft.Xna.Framework.GamerServices.FriendGamer クラスのオブジェクトを取得できます。

■ Microsoft.Xna.Framework.GamerServices.FriendGamer クラス

public sealed class FriendGamer : Gamer

このクラスは、フレンドを表すアカウント情報を提供します。Gamer から派生しているため、SignedInGamer と同じようにゲーマータグやプロファイルを取得することができます。加えて、フレンドがオンラインなのかどうかや、何をしているのかといった情報を取得することが可能です。

フレンドが現在オンラインかどうかを取得するには IsOnline プロパティを使います。

■ FriendGamer クラス IsOnline プロパティ

public bool IsOnline { get; }

このプロパティが true を返した場合、フレンドはオンラインです。ただし、こうしたフレンドの情報は実行中の間にも変更される可能性があります。フレンドの状態が変更されると、自動的に FriendGamer オブジェクトのプロパティが更新されます。最新の状態を常に得るには、Update() メソッドか Draw() メソッド内でプロパティにアクセスする形になるでしょう。

同じ形で、退席中かどうかを調べる IsAway プロパティや、取り込み中かどうかを調べる IsBusy プロパティなど、フレンドの状態を取得することができます。

■ Sample05

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private SpriteFont font;
    private string text;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        base.Initialize();
    }

    protected override void LoadContent()
    {
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        SignedInGamer gamer = Gamer.SignedInGamers[PlayerIndex.One];
        if (gamer != null)
        {
            FriendCollection friends = gamer.GetFriends();
            text = "Online friends\n";

            for (int i = 0; i < friends.Count; i++)
            {
                FriendGamer friend = friends[i];
                if (friend.IsOnline)
                {
                    text += "    " + friend.Gamertag;
                    if (friend.IsAway) text += ", Away";
                    if (friend.IsBusy) text += ", Busy";
                    text += "\n";
                }
            }
        }
        else text = "Please sign as player one.";
        sprite.Begin();
        sprite.DrawString(font, text, Vector2.Zero, Color.Black);
        sprite.End();
        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample05 は、オンライン状態のフレンドを列挙するプログラムです。PlayerIndex.One がサインインしているかどうかを調べ、サインインしている場合はフレンドのコレクションを取得し、各フレンドがオンラインかどうかを調べています。フレンドがオンラインの場合、text 変数にフレンドのゲーマータグを加えます。また、退席中の場合は Away、取り込み中の場合は Busy という文字列をゲーマータグの後に加えます。

非同期によるプロファイルの読み込み

サインインしているゲーマータグのプロファイルは、すでにローカルシステム上にデータがあるため取得に時間のかかるものではありません。GetProfile() メソッドは、すぐに結果を返すことができるでしょう。ところが、フレンドのゲーマータグに関連付けられているプロファイルを取得するには、ネットワークからのダウンロードとなります。GetProfile() メソッドを使ってプロファイルを取得するという方法は同じですが、かかる時間は異なります。もし、すべてのフレンドのプロファイルを取得するようなコードを書いた場合、フレンドの数によっては非常に時間がかかってしまいます。

次のコードを実行してみてください。

■Sample06
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private List<Texture2D> icons;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
        icons = new List<Texture2D>();
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        SignedInGamer.SignedIn += delegate(object sender, SignedInEventArgs e)
        {
            foreach (FriendGamer friend in e.Gamer.GetFriends())
            {
                GamerProfile profile = friend.GetProfile();
                icons.Add(profile.GamerPicture);
            }
        };
        SignedInGamer.SignedOut += delegate(object sender, SignedOutEventArgs e)
        {
            icons.Clear();
        };
        base.Initialize();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        Vector2 position = Vector2.Zero;
        sprite.Begin();
        foreach (Texture2D image in icons)
        {
            sprite.Draw(image, position, Color.White);
            position.X += image.Width;
            if (position.X + image.Width > GraphicsDevice.Viewport.Width)
            {
                position.X = 0;
                position.Y += image.Height;
            }
        }
        sprite.End();

        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample06 は、フレンドのアイコンを画面に列挙するというプログラムです。サインインしたときに発生する SignedIn イベントで、全てのフレンドのプロファイルを取得し、アイコンの Texture2D オブジェクトを icons コレクションに追加しています。このとき、GetFriends() メソッドが返したフレンドのコレクションを foreach 文で順に処理しています。通常、1 人分のプロファイルのダウンロードは数秒で終わりますが、数十人のフレンドが登録されている場合は時間がかかります。

GetFriends() メソッドは、プロファイルが完全にダウンロードされるまで結果を返さない同期メソッドです。そのため、SignedIn イベントが発生すると、すべてのフレンドのプロファイルが返されるまでゲームは制御を返さなくなり、ゲームがビジー状態となってしまいます。Sample06 の場合、実行結果のサインイン画面でゲームが停止し、しばらくの間、動かなくなってしまいます。この問題を解決するには、非同期でプロファイルをダウンロードする BeginGetProfile() メソッドと EndGetProfile() を使います。

BeginGetProfile() メソッドは、プロファイルのダウンロードを開始させ、コールバック用のデリゲートを登録します。このメソッドは、ダウンロードの終了を待たずに制御を返します。

■ Gamer クラス BeginGetProfile() メソッド

public IAsyncResult BeginGetProfile (
         AsyncCallback callback,
         Object asyncState
)

callback には、プロファイルのダウンロードが終了した時に呼び出される AsyncCallback 型のデリゲートを指定します。asyncState には、非同期操作に使用する任意のユーザー定義のオブジェクトを指定します。BeginGetProfile() メソッドは、プロファイルのダウンロードを待たずに IAsyncResult オブジェクトを返して終了します。このオブジェクトを使って、非同期操作の進捗状況を確認できます。

ダウンロードした(している)プロファイルは、EndGetProfile() メソッドから取得します。

■ Gamer クラス EndGetProfile() メソッド

public GamerProfile EndGetProfile (
         IAsyncResult result
)

このメソッドのパラメータ result には、BeginGetProfile() メソッドが返した IAsyncResult オブジェクトを渡します。メソッドは、非同期操作で取得したプロファイルを返します。プロファイルのダウンロードが終了していない場合、EndGetProfile() メソッドは、処理が完全に終了するまで制御を返しません。

通常は、BeginGetProfile() メソッドの callback パラメータに指定した AsyncCallback デリゲートのメソッドが呼び出されるのを待機し、メソッドが呼び出された時点で EndGetProfile() メソッドを呼び出します。この仕組みを利用することで、ダウンロードできたプロファイルから順に利用するといったことができるようになります。いつ終わるのかもわからないダウンロード処理の間、すべてのデータが手に入るまでプレイヤーを待たせるのは賢明ではありません。

AsyncCallback や IAsyncResult による非同期操作の詳細は、XNA Framework ではなく .NET Framework の管轄となるため、本稿での詳細は割愛させていただきます。詳しくは MSDN の System 名前空間、および「非同期プログラミングのデザイン パターン」などを参考にしてください。

■ Sample07

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private List<Texture2D> icons;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
        icons = new List<Texture2D>();
    }

    private void AsyncCallback(IAsyncResult ar)
    {
        FriendGamer friend = (FriendGamer)ar.AsyncState;
        GamerProfile profile = friend.EndGetProfile(ar);
        icons.Add(profile.GamerPicture);
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        SignedInGamer.SignedIn += delegate(object sender, SignedInEventArgs e)
        {
            foreach (FriendGamer friend in e.Gamer.GetFriends())
            {
                friend.BeginGetProfile(AsyncCallback, friend);
            }
        };
        SignedInGamer.SignedOut += delegate(object sender, SignedOutEventArgs e)
        {
            icons.Clear();
        };
        base.Initialize();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        Vector2 position = Vector2.Zero;
        sprite.Begin();
        foreach (Texture2D image in icons)
        {
            sprite.Draw(image, position, Color.White);
            position.X += image.Width;
            if (position.X + image.Width > GraphicsDevice.Viewport.Width)
            {
                position.X = 0;
                position.Y += image.Height;
            }
        }
        sprite.End();

        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample07 は、Sample06 を改良して、非同期操作でプロファイルを取得しています。そのため、サインインの後にゲームが停止することはありません。ダウンロードできたプロファイルのアイコンから順に画面に表示されることを確認してください。結果は同じですが、Sample07 はダウンロードの間もゲームを操作できるためストレスを溜めることがなくなります。

データの保存

ゲームデータの保存や読み込みは、基本的には XNA Framework の管轄ではなく .NET Framework を利用することになります。ただし、XNA Framework はシステムを隠蔽しているため、コード上にファイルパスを直接指定することは避けなければなりません。そこで、プレイヤーにデータを保存するストレージ機器を選択させる機能が用意されています。選択されたストレージ機器と、設定されたタイトルなどから、システムが適切なパスを提供してくれます。

ストレージ機器の選択を表示するには BeginShowStorageDeviceSelector() メソッドを使います。このメソッドは、非同期メソッドです。メソッドを呼び出すと、プレイヤーがストレージ機器を選択する前に制御を返します。

■ Guide クラス BeginShowStorageDeviceSelector() メソッド

public static IAsyncResult BeginShowStorageDeviceSelector (
         AsyncCallback callback,
         Object state
)

基本的な構造は、プロファイルの BeginGetProfile() と同じです。callback パラメータには、ストレージ機器の選択が終了したときに呼び出されるデリゲートを指定します。state パラメータには、ユーザー定義の任意のデータを設定することができます。メソッドは、非同期操作の状態を提供する IAsyncResult オブジェクトを返します。

Xbox 360 で実行した場合、ストレージ機器を選択するためのブレードが表示されます。プレイヤーがストレージ機器を選択した時点で callback パラメータに指定したデリゲートが呼び出されます。Windows PC の場合はストレージ機器の選択は行われず、ドキュメントが配置されているドライブが使われます。そのため、メソッドはすぐに操作を終了します。

選択されたストレージ機器の情報を取得するには EndShowStorageDeviceSelector() メソッドを使います。BeginShowStorageDeviceSelector() と EndShowStorageDeviceSelector() によるストレージ機器の選択の仕組みは、BeginGetProfile() メソッドと EndGetProfile() メソッドによるプロファイルの取得と同じです。

■ Guide クラス EndShowStorageDeviceSelector() メソッド

public static StorageDevice EndShowStorageDeviceSelector (
         IAsyncResult asyncResult
)

asyncResult パラメータには、BeginShowStorageDeviceSelector() メソッドが返した IAsyncResult を指定します。ストレージ機器の選択が終了していれば、選択したストレージ機器の情報を表す Microsoft.Xna.Framework.Storage.StorageDevice クラスのオブジェクトが返されます。

■ Microsoft.Xna.Framework.Storage.StorageDevice クラス

public sealed class StorageDevice

このクラスは、選択されたストレージ機器の情報を提供します。Xbox 360 の場合、選択されるストレージ機器は本体に取り付けられている HDD か、Xbox 360 専用メモリーカードのいずれかになります。

ストレージ機器の情報は、StorageDevice クラスのプロパティから取得できます。ストレージ機器の総容量は TotalSpace プロパティから、利用可能な容量は FreeSpace プロパティからバイト単位で取得できます。データを保存する場合、必要な空き容量が確保できているかどうかを調べる必要があります。

■ StorageDevice クラス TotalSpace  プロパティ

public long TotalSpace { get; }

■ StorageDevice クラス FreeSpace プロパティ

public long FreeSpace { get; }

選択されたストレージ機器から、データを保存するためのパスを取得するには OpenContainer() メソッドを使います。

■ StorageDevice クラス OpenContainer() メソッド

public StorageContainer OpenContainer (
         string titleName
)

titleName には、XNA ゲームのタイトルを指定します。このタイトルに基づいて、XNA Framework は適切なパスを用意します。実体となるファイルは、このタイトルをコンテナに配置されます。Windows PC の場合、ドキュメント内に SavedGames という名前のフォルダが生成され、その中に titleName に指定した名前のフォルダが作られます。さらに、その中にプレイヤーごとのフォルダが作られます。

ストレージ機器に作られるファイルは、OpenContainer() メソッドが返した Microsoft.Xna.Framework.Storage.StorageContainer クラスを論理的なコンテナとします。ゲームデータは、このコンテナ内に保存されます。

■ Microsoft.Xna.Framework.Storage.StorageContainer クラス

public sealed class StorageContainer : IDisposable

ファイルのパスを表す文字列は、Path プロパティから取得できます。

■ StorageContainer クラス Path プロパティ

public string Path { get; }

これで、ファイルを作成する場所を確定することができます。

■ Sample08

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Storage;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private SpriteFont font;
    private string text;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        text = "Please push X button and show storage device selector.";
        base.Initialize();
    }

    protected override void LoadContent()
    {
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    private void AsyncCallback(IAsyncResult ar)
    {
        StorageDevice storageDevice = Guide.EndShowStorageDeviceSelector(ar);
        StorageContainer storageContainer = storageDevice.OpenContainer("Test");
        text = "Total space=" + storageDevice.TotalSpace / 1048576 + "MB\n" +
            "Free space=" + storageDevice.FreeSpace / 1048576 + "MB\n" +
            "Title=" + storageContainer.TitleName + "\n" +
            "Path=" + storageContainer.Path;
        storageContainer.Dispose();
    }

    protected override void Update(GameTime gameTime)
    {
        GamePadState state = GamePad.GetState(PlayerIndex.One);
        if (!Guide.IsVisible && state.Buttons.X == ButtonState.Pressed)
        {
            Guide.BeginShowStorageDeviceSelector(PlayerIndex.One, AsyncCallback, null);
        }
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin();
        sprite.DrawString(font, text, Vector2.Zero, Color.Black);
        sprite.End();

        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample08 は、選択されたストレージ機器の総容量と空き容量などの情報を表示するプログラムです。このプログラムは、ストレージ機器に対してデータを書き込むことはありません。Windows PC 上で実行した場合、ストレージ機器を選択する画面は表示されず、実行結果からも確認できるようにドキュメント内にフォルダが作られます。これに対し、Xbox 360 上で実行した場合は、ストレージ機器を選択するブレードが表示されます。

次に、StorageContainer クラスが指すパス上にファイルを作成し、任意のデータを書き込んでみましょう。.NET Framework の System.IO 名前空間にある File クラスなどを使ってファイルを生成することができます。StorageContainer が指すパス内に任意の名前のファイルを作成するには Path クラスの Combine() メソッドを使うと便利です。

■ Sample09

using System;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Storage;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphics;
    private SpriteBatch sprite;
    private SpriteFont font;
    private string text;

    public Test()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
    }

    protected override void Initialize()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        text = "Please push X button and show storage device selector.";
        base.Initialize();
    }

    protected override void LoadContent()
    {
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    private void AsyncCallback(IAsyncResult ar)
    {
        StorageDevice storageDevice = Guide.EndShowStorageDeviceSelector(ar);
        StorageContainer storageContainer = storageDevice.OpenContainer("Test");
        FileStream stream = File.Create(Path.Combine(storageContainer.Path, "test.txt"));
        TextWriter writer = new StreamWriter(stream);
        writer.Write("Your potential. Our passion.");
        writer.Close();
        stream.Close();
        storageContainer.Dispose();

        text = "The text was written to\n" + storageContainer.Path;
    }

    protected override void Update(GameTime gameTime)
    {
        GamePadState state = GamePad.GetState(PlayerIndex.One);
        if (!Guide.IsVisible && state.Buttons.X == ButtonState.Pressed)
        {
            Guide.BeginShowStorageDeviceSelector(AsyncCallback, null);
        }
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin();
        sprite.DrawString(font, text, Vector2.Zero, Color.Black);
        sprite.End();

        base.Draw(gameTime);
    }
}

■ 実行結果

実行結果

Sample09 は、選択されたストレージ機器に Test という名前のタイトルで test.txt という名前のテキストファイルを生成します。テキストファイル内には、StreamWriter を使って文字列を出力しています。Windows PC で実行すると、ドキュメント内にファイルが生成されているので、テキストエディタなどで開いて確認することができます。Xbox 360 の場合、ファイルを開くことはできませんが、選択したメモリ内にデータが生成されていることが確認できます。

マインスイーパーの場面分割

さて、今回は Live サービスの機能を中心としたため、直接マインスイーパーの機能に関連する内容ではありませんでした。これまで作成したゲームの起動時に GamerServicesComponent クラスのオブジェクトをゲーム部品として登録するだけで、ゲームはサインインなどの Live サービスに対応することができます。ただし、マインスイーパーのゲーム内でゲーマータグに関連した情報を使うことはないでしょう。ネットワーク通信の詳細は次回にご紹介するとして、今回はプレイ時間を計測して、ゲームクリアやゲームオーバー時の処理を実装し、最高記録を保存する仕組みを作りましょう。

これまでは、マインスイーパーのプレイ場面のみを作りましたが、実際のゲームにはタイトル画面やクレジット、設定など、いくつかの場面に分離され、それぞれの場面が操作によって遷移する仕組みを持ちます。ゲームのクリアやゲームオーバーの場面なども作る必要があります。そこで、まずはゲームをいくつかの場面に分離する作業から始めましょう。

ゲームの複雑な場面遷移の処理を、すべて Update() メソッドや Draw() メソッドに書き込むのは賢明ではありません。if 文が深い層までネストし、メソッドが複雑になってしまいます。より本格的なゲームでは、再利用性や役割の分担などの観点から GameComponent として場面を構築する方法が考えられますが、カードゲームや本稿のマインスイーパーのようなミニゲームであれば、各場面ごとのメソッドを作成して処理を分離する方法で十分でしょう。

作成するマインスイーパーには、タイトル、プレイ、ゲームオーバー、クリアの 4 つの状態があるものとし、それぞれをゲームの状況に応じて正しく遷移するようにプログラムします。状態が変更されると、ゲームは現在の状態に対応する更新や描画メソッドを呼び出すものとします。ゲームの状態は GameState 列挙型で表すものとします。

■ GameState 列挙型

public enum GameState
{
    Unknown = 0, Title, Playing, GameOver, Clear
}

ゲームはタイトル場面の状態を表す Title から始まり、コントローラの操作によって状態を変更してプレイ場面を表す Playing に移行します。その後、地雷を踏んだ場合は GameOver 状態に、地雷以外のすべてのマスを開くことができれば Clear 状態に移行し、再びタイトルまで戻ります。

 各ゲームの状態には、場面ごとに使われるデータを持ちます。そこで、状態が変更される直前に、初期化処理を挟むと便利です。次のようなメソッドを用意し、状態を移行させる前に必ず呼び出すように仕組みます。

■ Minesweeper クラス InitTitle() メソッド

private void InitTitle()

■ Minesweeper クラス InitPlaying() メソッド

private void InitPlaying()

■ Minesweeper クラス InitGameOver() メソッド

private void InitGameOver()

■ Minesweeper クラス InitClear() メソッド

private void InitClear()

これらのメソッドでは、目的の場面で使われるデータを初期化します。

各状態での更新処理は、初期化処理と同じように専用の Update() メソッドを作成します。

■ Minesweeper クラス UpdateTitle() メソッド

private void UpdateTitle(GameTime gameTime, GamePadState padState)

■ Minesweeper クラス UpdatePlaying() メソッド

private void UpdatePlaying(GameTime gameTime, GamePadState padState)

■ Minesweeper クラス UpdateGameOver() メソッド

private void UpdateGameOver(GameTime gameTime, GamePadState padState)

■ Minesweeper クラス UpdateClear() メソッド

private void UpdateClear(GameTime gameTime, GamePadState padState)

どのような状態であっても、コントローラからの入力を処理することになるため、あらかじめ Update() メソッド内で GamePadState を取得して padState パラメータに渡すものとします。状態の遷移処理も、上記の更新メソッド内で入力に応じて行われます。Update() メソッドでは、ゲームの現在の状態に従って対応する上記のメソッドを呼び出すように仕掛けます。次のようなコードを Update() メソッド内の適切な場所に記述することになります。

GamePadState padState = GamePad.GetState(PlayerIndex.One);

if (GameState == GameState.Title) UpdateTitle(gameTime, padState);
else if (GameState == GameState.Playing) UpdatePlaying(gameTime, padState);
else if (GameState == GameState.GameOver) UpdateGameOver(gameTime, padState);
else if (GameState == GameState.Clear) UpdateClear(gameTime, padState);

描画処理も同じように状態ごとの Draw() メソッドを用意します。このように、場面ごとに処理を分割し、他のコードへの依存を最小限にしておくことで、ロジックやデザインの変更に強いゲームを作ることができます。

■ Minesweeper クラス () メソッド

private void DrawTitle(GameTime gameTime)

■ Minesweeper クラス () メソッド

private void DrawPlaying(GameTime gameTime)

■ Minesweeper クラス () メソッド

private void DrawGameOver(GameTime gameTime)

■ Minesweeper クラス () メソッド

private void DrawClear(GameTime gameTime)

各状態の場面の描画は自由に行ってください。このように設計すれば、最初からすべての場面を作り込む必要はなく、デバッグ用の簡単な描画処理だけを行い、画像やモデルなどのリソースが完成した時点で組み込んでいくことができます。

最後に、ゲームの状態に応じて Draw() メソッドから適切な描画メソッドを呼び出します。

if (GameState == GameState.Title) DrawTitle(gameTime);
else if (GameState == GameState.Playing) DrawPlaying(gameTime);
else if (GameState == GameState.GameOver) DrawGameOver(gameTime);
else if (GameState == GameState.Clear) DrawClear(gameTime);

Playing 状態の他に GameOver や Clear 状態でも地雷原は描画し続けたいので、地雷原の描画処理を DrawMineField() に分離します。複数の場面で使われる処理は、このようにさらに分離する必要がありますが、関係が複雑になるような場合は別のクラスとしてコンポーネント化する方法も検討した方が良いでしょう。設定場面やメッセージの表示など、どこからでも呼び出せる機能などは、GameComponent として実装した方が柔軟な利用が可能です。

記録の保存

次に、ゲームのプレイ時間を計測し、最短でクリアした時間を保存する仕組みを作りましょう。記録は、選択されたストレージ機器に保存し、ゲームを終了してもデータが残るようにします。再びゲームを起動したときに、ストレージ機器から記録を読み込むことができます。

ストレージ機器の選択は、タイトル画面で最初にゲームを始めるときに行いましょう。あらかじめ、StorageDevice 型のフィールドを用意し、このフィールドの値が null であれば BeginShowStorageDeviceSelector() メソッドを呼び出します。一度ストレージ機器を選択すれば、2 回目以降は不要とします。この処理は UpdateTitle() メソッド内に次のように記述します。

if (padState.Buttons.Start == ButtonState.Pressed)  //ゲーム開始
{
    if (storageDevice == null)
    {
        //ストレージの選択
        Guide.BeginShowStorageDeviceSelector(CallbackShowStorageDeviceSelector, null);
    }
    else
    {
        GameState = GameState.Playing;
        InitPlaying();
    }
}

ストレージ機器が選択された時に呼び出される CallbackShowStorageDeviceSelector() メソッドを用意し、BeginShowStorageDeviceSelector() メソッドのパラメータに設定しています。CallbackShowStorageDeviceSelector() メソッドでは、選択されたストレージ機器を取得し、ファイルが存在していればデータを読み込みます。その後、ゲームの状態を Playing に移行します。

private void CallbackShowStorageDeviceSelector(IAsyncResult ar)
{
    //選択されたストレージ機器の取得
    storageDevice = Guide.EndShowStorageDeviceSelector(ar);

    //このゲームのタイトルでコンテナを取得
    StorageContainer storageContainer = storageDevice.OpenContainer(Window.Title);
    string filePath = Path.Combine(storageContainer.Path, RECORD_FILE);

    //ファイルが存在していれば記録を読み込む
    if (File.Exists(filePath))
    {
        FileStream stream = File.OpenRead(filePath);
        BinaryReader reader = new BinaryReader(stream);
        long ticks = reader.ReadInt64();
        reader.Close();
        stream.Close();

        recordTime = new TimeSpan(ticks);
    }

    storageContainer.Dispose();

    GameState = GameState.Playing;
    InitPlaying();
}

Exists() メソッドで取得したパスにファイルが存在するかどうかを調べ、存在する場合は BinaryReader を使ってファイルから記録されている 64 ビットの整数を読み込みます。この値は TimeSpan 構造体の Ticks プロパティの値を保存したものとします。ファイルが存在しない場合は、デフォルトの記録を使うことにしましょう。

ゲームをクリアしたときに実行される InitClear() メソッド内で最高記録とプレイ時間を比較し、プレイ時間が最高記録を超えていれば、選択されているストレージ機器に新しい記録として保存します。最短クリア時間の記録は、BinaryWriter クラスを使って Ticks プロパティの 64 ビット整数を保存します。

if (playTime.Ticks < recordTime.Ticks)
{
    stateText += "New record time [" + playTime.TotalSeconds + "] !\n";
    StorageContainer storageContainer = storageDevice.OpenContainer(Window.Title);
    string filePath = Path.Combine(storageContainer.Path, RECORD_FILE);

    FileStream stream = File.Create(filePath);
    BinaryWriter writer = new BinaryWriter(stream);
    writer.Write(playTime.Ticks);
    writer.Close();
    stream.Close();

    storageContainer.Dispose();
    recordTime = playTime;
}

もちろん、実践のゲームで保存するデータはもう少し複雑になるでしょう。プレイヤーのゲーマータグと記録を関連付けたり、複数の記録をランキングとして並べたりするといった方法が考えられます。より複雑なデータを取る場合は、Minesweeper クラスに処理を記述すると複雑になってしまうため、別のデータ管理用のクラスを作成する方が賢明です。

■ 実行結果

実行結果



Top of Page Top of Page
Visual Studio 2008 Express

Microsoft