次の方法で共有



November 2011

Volume 26 Number 11

Windows Phone SDK 7.1 - "Mango" アプリケーションをビルドする

Andrew Whitechapel | November 2011

"Mango" (マンゴー) は、Windows Phone SDK 7.1 リリースの社内でのコード ネームです。もちろんおいしいトロピカル フルーツの名前でもあります。パイ、サラダ、さまざまなカクテルなど、マンゴーには多数の用途があります。また、さまざまな点で健康に良いとされ、栽培に関する興味深い歴史もあります。この記事では、Mangolicious というマンゴーをテーマにした Windows Phone SDK 7.1 アプリケーションについて説明します。このアプリケーションでは、さまざまなマンゴーのレシピ、カクテル、および豆知識を得ることができますが、本来の目的は、次のような 7.1 リリースの重要な新機能をいくつか紹介することです。

  • ローカル データベースと LINQ to SQL
  • セカンダリ タイルとディープ リンクの設定
  • Silverlight/XNA の統合

このアプリケーションのユーザー エクスペリエンスは単純です。メイン ページにパノラマを使用し、最初のパノラマ項目にはメニューを表示し、2 つ目のパノラマ項目には今の季節のレシピとカクテルを動的に選択して表示し、3 つ目のパノラマ項目には簡単なバージョン情報を表示します (図 1 参照)。

Mangolicious のパノラマ メイン ページ
図 1 Mangolicious のパノラマ メイン ページ

"季節の注目メニュー" セクションのメニューと各項目は、どちらもアプリケーションのほかのページに移動するリンクとして機能します。大半のページはわかりやすい Silverlight ページで、1 ページだけ統合 XNA ゲーム専用のページがあります。以下に、このアプリケーションのビルドに必要な作業の概要を、最初から最後まで順に示します。

  1. 基礎となるソリューションを Visual Studio で作成します。
  2. レシピ、カクテル、および豆知識に関するデータのデータベースを別途作成します。
  3. このデータベースを使用するようにアプリケーションを更新し、データ バインド用に公開します。
  4. さまざまな UI ページを作成し、データ バインドします。
  5. セカンダリ タイル機能を設定して、ユーザーがレシピ項目を携帯電話のスタート ページに固定できるようにします。
  6. アプリケーションに XNA ゲームを組み込みます。

ソリューションを作成する

このアプリケーションでは、Visual Studio の Windows Phone Silverlight and XNA Application (Windows Phone Silverlight/XNA アプリケーション) テンプレートを使用します。このテンプレートにより、3 つのプロジェクトを備えたソリューションが生成されます。名前を変更後のソリューションの概要を図 2 に示します。

図 2 Windows Phone Silverlight/XNA ソリューションのプロジェクト

プロジェクト 説明
MangoApp 既定の MainPage と補助的な GamePage を備えた、携帯電話アプリケーション本体を含みます。
GameLibrary すべての正しい参照設定を含めていますが、コードは含まない、事実上、空のプロジェクトです。重要な点として、コンテンツ プロジェクトへのコンテンツ参照を含みます。
GameContent すべてのゲーム資産 (画像、音声ファイルなど) を保持する空のコンテンツ プロジェクトです。

データベースと DataContext クラスを作成する

Windows Phone SDK 7.1 リリースでは、ローカル データベースのサポートが導入されています。つまり、アプリケーションから携帯電話のローカル データベース ファイル (SDF) にデータを保存できるようになります。アプリケーション自体の一部として、またはデータベース作成専用にビルドした別のヘルパー アプリケーションを利用して、コードを使ってデータベースを作成するのが推奨アプローチです。ほとんど、またはすべてのデータをアプリケーションの実行時にのみ作成するシナリオでは、アプリケーション内にデータベースを作成する方が合理的です。Mangolicious アプリケーションの場合、静的データだけを使用するため、事前にデータをデータベースに格納します。

そのため、単純な Windows Phone Application (Windows Phone アプリケーション) テンプレートに基づく、別のデータベース作成用ヘルパー アプリケーションを作成します。コードでデータベースを作成するには、DataContext から派生したクラスが必要です。DataContext は、Windows Phone 向けカスタム バージョンの System.Data.Linq アセンブリで定義されています。データベースを作成するヘルパー アプリケーションと、データベースを使用するメイン アプリケーションの両方で、この同じ DataContext クラスを使用します。携帯電話アプリケーションから書き込み可能な場所は分離ストレージだけなので、ヘルパー アプリケーションでは、データベースの場所が分離ストレージになるよう指定しなければなりません。このクラスは、各データベース テーブルに対応する一連の Table 型フィールドを保持します。

public class MangoDataContext : DataContext
{
  public MangoDataContext()
    : base("Data Source=isostore:/Mangolicious.sdf") { }
 
  public Table<Recipe> Recipes;
  public Table<Fact> Facts;
  public Table<Cocktail> Cocktails;
}

コードの Table 型クラスと、データベースのテーブルは、1 対 1 に対応します。Column プロパティは、データベース テーブルの列に対応しており、データ型やサイズ (INT、NVARCHAR など)、列を null にできるかどうか、キー列かどうかなどを示すデータベース スキーマ プロパティを含みます。データベースに含まれる他の全テーブルの Table 型クラスについても同じ方法で定義します (図 3 参照)。

図 3 Table 型クラスの定義

[Table]
public class Recipe
{
  private int id;
  [Column(
    IsPrimaryKey = true, IsDbGenerated = true,
    DbType = "INT NOT NULL Identity", CanBeNull = false,
    AutoSync = AutoSync.OnInsert)]
  public int ID
  {
    get { return id; }
    set
    {
      if (id != value)
      {
        id = value;
      }
    }
  }
 
  private string name;
  [Column(DbType = "NVARCHAR(32)")]
  public string Name
  {
    get { return name; }
    set
    {
      if (name != value)
      {
        name = value;
      }
    }
  }?
    ... additional column definitions omitted for brevity
}

ただし、ヘルパー アプリケーションでは (また、標準のモデル - ビュー - ビューモデル (MVVM: Model-View-ViewModel) 手法を使用する場合は)、DataContext クラスを使用してビュー (UI) とモデル (データ) を仲介するビューモデル クラスが必要になります。ビューモデルは、DataContext フィールドと、テーブル データの一連のコレクション (Recipes、Facts、および Cocktails) を保持します。データが静的なので、ここではシンプルな List<T> コレクションで十分です。同じ理由から、必要なのは get プロパティ アクセサーだけで、set 修飾子は必要ありません (図 4 参照)。

図 4 ビューモデルにおけるテーブル データのコレクション プロパティの定義

public class MainViewModel
{
  private MangoDataContext mangoDb;
 
  private List<Recipe> recipes;
  public List<Recipe> Recipes
  {
    get
    {
      if (recipes == null)
      {
        recipes = new List<Recipe>();
      }
    return recipes;
    }
  }
 
    ... additional table collections omitted for brevity
}

また、データベースとすべてのデータを実際に作成するために、UI から呼び出せるパブリック メソッドを公開します。このメソッドでは、データベースが存在しない場合はデータベース自体を作成し、各テーブルを順番に作成して、作成した各テーブルに静的データを格納します。たとえば、Recipe テーブルを作成するには、まずテーブルの行に対応する Recipe クラスのインスタンスを複数作成し、次にコレクション内のすべての行を DataContext に追加し、最後にデータをデータベースにコミットします。Facts テーブルと Cocktails テーブルについても、同じ方法で作成します (図 5 参照)。

図 5 データベースの作成

public void CreateDatabase()
{
  mangoDb = new MangoDataContext();
  if (!mangoDb.DatabaseExists())
  {
    mangoDb.CreateDatabase();
    CreateRecipes();
    CreateFacts();
    CreateCocktails();
  }
}
 
private void CreateRecipes()
{
  Recipes.Add(new Recipe
  {
    ID = 1,
    Name = "key mango pie",
    Photo = "Images/Recipes/MangoPie.jpg",
    Ingredients = "2 cans sweetened condensed milk, ¾ cup fresh key lime juice, ¼ cup mango purée, 2 eggs, ¾ cup chopped mango.",
    Instructions = "Mix graham cracker crumbs, sugar and butter until well distributed. Press into a 9-inch pie pan. Bake for 20 minutes. Make filling by whisking condensed milk, lime juice, mango purée and egg together until blended well. Stir in fresh mango. Pour filling into cooled crust and bake for 15 minutes.",
    Season = "summer"
  });
 
    ... additional Recipe instances omitted for brevity
 
  mangoDb.Recipes.InsertAllOnSubmit<Recipe>(Recipes);
  mangoDb.SubmitChanges();
}

ヘルパー アプリケーションの適切な位置 (おそらくはボタンのクリック ハンドラー) で、この CreateDatabase メソッドを呼び出します。ヘルパーを (エミュレーターまたは物理デバイスで) 実行すると、アプリケーションの分離ストレージにデータベース ファイルが作成されます。最後の作業として、メイン アプリケーションで使用できるようにこのデータベース ファイルをデスクトップに抽出します。そのためには、Windows Phone SDK 7.1 に付属するコマンド ライン ツールの Isolated Storage Explorer ツールを使用します。エミュレーターからデスクトップに分離ストレージのスナップショットを作成するコマンドは、次のとおりです。

"C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool\ISETool" ts xd {e0e7e3d7-c24b-498e-b88d-d7c2d4077a3b} C:\Temp\IsoDump

このコマンドは、ツールが標準の場所にインストールされていると想定しています。パラメーターについては、図 6 で説明します。

図 6 Isolated Storage Explorer のコマンド ライン パラメーター

パラメーター 説明
ts "Take snapshot" (スナップショットの作成) の省略形 (分離ストレージからデスクトップにダウンロードするコマンドです)。
xd XDE (エミュレーター) の省略形。
{e0e7e3d7-c24b-498e-b88d-d7c2d4077a3b} ヘルパー アプリケーションの ProductID。この値は WMAppManifest.xml に記載され、アプリケーションごとに異なります。
C:\Temp\IsoDump スナップショットのコピー先となる、デスクトップ上の任意のパス。

SDF ファイルをデスクトップに抽出すれば、ヘルパー アプリケーションは完成し、このデータベースを使用する Mangolicious アプリケーションに取り掛かることができます。

データベースを使用する

Mangolicious アプリケーションでは、SDF ファイルをプロジェクトに追加し、同じカスタム DataContext クラスをソリューションに追加して、2 点の細かい変更を行います。まず、Mangolicious ではデータベースに書き込む必要がないため、アプリケーションのインストール フォルダーから直接データベースを使用します。そのため、接続文字列はヘルパー アプリケーションの接続文字列とはやや異なります。また、Mangolicious では SeasonalHighlights テーブルをコードで定義します。データベースには、対応する SeasonalHighlights テーブルは存在しません。代わりに、このコード テーブルでは基になる 2 つのデータベース テーブル (Recipes と Cocktails) からデータを取得し、このコード テーブルを使用して "季節の注目メニュー" のパノラマ項目を設定します。データベース作成用ヘルパー アプリケーションと Mangolicious データベース使用アプリケーションにおける DataContext クラスの違いは、この 2 点だけです。

public class MangoDataContext : DataContext
{
  public MangoDataContext()
    : base("Data Source=appdata:/Mangolicious.sdf;File Mode=read only;") { }
 
  public Table<Recipe> Recipes;
  public Table<Fact> Facts;
  public Table<Cocktail> Cocktails;
  public Table<SeasonalHighlight> SeasonalHighlights;
}

Mangolicious アプリケーションにもビューモデル クラスが必要で、ヘルパー アプリケーションのビューモデル クラスを出発点として使用できます。ここでも、先ほどの DataContext フィールドと、データ テーブルの一連の List<T> コレクション プロパティが必要です。まずは、コンストラクターで現在の季節を判断し記録する、文字列プロパティを追加します。

public MainViewModel()
{
  season = String.Empty;
  int currentMonth = DateTime.Now.Month;
  if (currentMonth >= 3 && currentMonth <= 5) season = "spring";
  else if (currentMonth >= 6 && currentMonth <= 8) season = "summer";
  else if (currentMonth >= 9 && currentMonth <= 11) season = "autumn";
  else if (currentMonth == 12 || currentMonth == 1 || currentMonth == 2)
    season = "winter";
}

ビューモデルに不可欠なメソッドは、LoadData メソッドです。このメソッドでは、データベースを初期化し、LINQ to SQL クエリを実行して、DataContext 経由でデータをメモリ内のコレクションに読み込みます。この時点で 3 つのテーブルすべてを事前に読み込むこともできますが、起動時のパフォーマンスを最適化することを優先して、関連ページを実際に表示するまでデータの読み込みを遅延します。SeasonalHighlights テーブルのデータだけはメイン ページに表示する必要があるため、起動時に読み込む必要があります。このテーブルにデータを読み込むために、2 つのクエリを作成して、Recipes テーブルと Cocktails テーブルから現在の季節に一致する行だけを選択し、まとめた行セットをコレクションに追加します (図 7 参照)。

図 7 起動時のデータ読み込み

public void LoadData()
{
  mangoDb = new MangoDataContext();
  if (!mangoDb.DatabaseExists())
  {
    mangoDb.CreateDatabase();
  }
 
  var seasonalRecipes = from r in mangoDb.Recipes
                        where r.Season == season
                        select new { r.ID, r.Name, r.Photo };
  var seasonalCocktails = from c in mangoDb.Cocktails
                          where c.Season == season
                          select new { c.ID, c.Name, c.Photo };
 
  seasonalHighlights = new List<SeasonalHighlight>();
  foreach (var v in seasonalRecipes)
  {
    seasonalHighlights.Add(new SeasonalHighlight {
      ID = v.ID, Name = v.Name, Photo = v.Photo, SourceTable="Recipes" });
  }
  foreach (var v in seasonalCocktails)
  {
    seasonalHighlights.Add(new SeasonalHighlight {
      ID = v.ID, Name = v.Name, Photo = v.Photo, SourceTable = "Cocktails" });
  }
 
  isDataLoaded = true;
}

同様の LINQ to SQL クエリを使用して LoadFacts メソッド、LoadRecipes メソッド、および LoadCocktails メソッドを個別に作成し、アプリケーションの起動後にこれらのメソッドを使用すると、対応するデータを必要に応じて読み込むことができます。

UI を作成する

メイン ページは、3 つの PanoramaItem を含むパノラマで構成します。最初の項目には、アプリケーションのメイン メニューを表示する ListBox を用意します。ユーザーが ListBox のいずれかの項目を選択したら、対応するページ、つまりレシピ、豆知識、またはカクテルのいずれかに関するコレクション ページ、あるいはゲーム ページに移動します。次のように、移動の直前に、対応するデータをレシピ、豆知識、またはカクテルのコレクションに読み込みます。

switch (CategoryList.SelectedIndex)
{
  case 0:
    App.ViewModel.LoadRecipes();
    NavigationService.Navigate(
      new Uri("/RecipesPage.xaml", UriKind.Relative));
    break;
 
... additional cases omitted for brevity
}

ユーザーが UI の "季節の注目メニュー" リストから項目を選択したら、選択した項目がレシピとカクテルのどちらなのかを調べ、項目の ID をナビゲーション クエリ文字列の一部として渡して、項目ごとのレシピ ページまたはカクテル ページに移動します (図 8 参照)。

図 8 "季節の注目メニュー" リストでの選択

SeasonalHighlight selectedItem =
  (SeasonalHighlight)SeasonalList.SelectedItem;
String navigationString = String.Empty;
if (selectedItem.SourceTable == "Recipes")
{
  App.ViewModel.LoadRecipes();
  navigationString =
    String.Format("/RecipePage.xaml?ID={0}", selectedItem.ID);
}
else if (selectedItem.SourceTable == "Cocktails")
{
  App.ViewModel.LoadCocktails();
  navigationString =
    String.Format("/CocktailPage.xaml?ID={0}", selectedItem.ID);
}
NavigationService.Navigate(
  new System.Uri(navigationString, UriKind.Relative));

ユーザーは、メイン ページのメニューから 3 つのリスト ページのいずれかに移動できます。各リスト ページでは、ビューモデルのコレクションの 1 つにデータ バインドして、レシピ、豆知識、またはカクテルのいずれかの項目のリストを表示します。各ページには単純な ListBox を配置し、リストの各項目には、写真用の Image コントロールと項目名用の TextBlock コントロールを含めます。たとえば、図 9 は FactsPage を示しています。

[fun facts] ページ (コレクション リスト ページの 1 つ)
図 9 [fun facts] ページ (コレクション リスト ページの 1 つ)

ユーザーがレシピ、豆知識、またはカクテルのリストから項目を 1 つ選択したら、ナビゲーション クエリ文字列で各項目の ID を渡して、項目ごとのレシピ、豆知識、またはカクテルページに移動します。項目ごとのページも、構成は 3 種類ともほぼ同じで、各ページには 1 つの Image コントロールを表示し、その下にテキストを表示します。データ バインドした TextBlock には明示的なスタイルを指定していませんが、すべての TextBlock で TextWrapping=Wrap を使用していることに注意してください。これは、App.xaml.cs で次のように TextBlock のスタイルを宣言するためです。

<Style TargetType="TextBlock" BasedOn="{StaticResource
  PhoneTextNormalStyle}">
  <Setter Property="TextWrapping" Value="Wrap"/>
</Style>

このようにスタイルを宣言すると、ソリューションのすべての TextBlock で、独自のスタイルを明示的に定義していない限り、このスタイルが使用されます。こうした暗黙のスタイル設定は、Silverlight 4 の一部として Windows Phone SDK 7.1にで導入された新機能の 1 つです。

各ページの分離コードは単純です。OnNavigatedTo オーバーライドでは、クエリ文字列から各項目の ID を抽出し、ビューモデル コレクションからその項目を見つけて、項目にデータ バインドします。RecipePage のコードは他のページより少し複雑です。このページの追加コードはすべて、ページの右上隅に配置している HyperlinkButton に関連しています。このボタンの例を図 10 に示します。

ボタンを表示したレシピ ページ
図 10 "ピン" ボタンを表示したレシピ ページ

セカンダリ タイル

ユーザーが項目ごとのレシピ ページで "ピン" の HyperlinkButton をクリックしたら、その項目をタイルとして携帯電話のスタート ページに固定します。項目の固定したら、スタート ページに移動し、アプリケーションを非アクティブにします。このようにタイルを固定すると、定期的にアニメーションが実行され、図 11図 12 に示すように前面と背面が切り替わります。

11 固定されているレシピのタイル (前面)
図 11 固定されているレシピのタイル (前面)

固定されているレシピのタイル (背面)
図 12 固定されているレシピのタイル (背面)

その後、ユーザーがこの固定したタイルをタップすると、アプリケーション内の特定の項目に直接移動します。項目のページに移動すると、"ピン" のボタンが "ピンをはずす" 画像に変わっていることがわかります。ページを固定解除すると、そのページがスタート ページに表示されなくなりますが、アプリケーションは続行します。

このしくみについて説明しましょう。RecipePage の OnNavigatedTo オーバーライドでは、データ バインド先の具体的なレシピ項目を特定する標準の処理を実行したら、次のコードを使用して、後でこのページの URL として使用できる文字列を作成します。

thisPageUri = String.Format("/RecipePage.xaml?ID={0}", recipeID);

"ピン" のボタンのボタン クリック ハンドラーでは、まずこのページのタイルが既に存在するかどうかを確認し、存在しない場合はこの時点で作成します。タイルは現在のレシピ データ (画像と名前) を使用して作成します。また、タイルの背面用に、単一の静的画像 (および静的テキスト) を設定します。同時に、"ピンをはずす" 画像を使用して、このタイミングでボタン自体を再描画します。一方、タイルが既に存在している場合は、ユーザーがタイルの固定解除を選択するためにクリック ハンドラーを呼び出したことになります。この場合は、タイルを削除し、"ピン" の画像を使用してボタンを再描画します (図 13 参照)。

図 13 ページの固定と固定解除

private void PinUnpin_Click(object sender, RoutedEventArgs e)
{
  tile = ShellTile.ActiveTiles.FirstOrDefault(
    x => x.NavigationUri.ToString().Contains(thisPageUri));
  if (tile == null)
  {
    StandardTileData tileData = new StandardTileData
    {
      BackgroundImage = new Uri(
        thisRecipe.Photo, UriKind.RelativeOrAbsolute),
      Title = thisRecipe.Name,
      BackTitle = "Lovely Mangoes!",
      BackBackgroundImage =
        new Uri("Images/BackTile.png", UriKind.Relative)
    };
 
    ImageBrush brush = (ImageBrush)PinUnpin.Background;
    brush.ImageSource =
      new BitmapImage(new Uri("Images/Unpin.png", UriKind.Relative));
    PinUnpin.Background = brush;
    ShellTile.Create(
      new Uri(thisPageUri, UriKind.Relative), tileData);
  }
  else
  {
    tile.Delete();
    ImageBrush brush = (ImageBrush)PinUnpin.Background;
    brush.ImageSource =
      new BitmapImage(new Uri("Images/Pin.png", UriKind.Relative));
    PinUnpin.Background = brush;
  }
}

注意が必要なのは、ユーザーが固定されているタイルをタップしてレシピ ページに移動した後で、携帯電話のハードウェア "戻る" ボタンを押すと、アプリケーション全体が終了することです。この動作は混乱を招く可能性があります。なぜなら、ユーザーは通常、メイン ページで "戻る" ボタンを押した場合にのみアプリケーションが終了し、他のページで押しても終了しないと予想するためです。ただし、代替策として、レシピ ページに一種の "ホーム" ボタンを配置して、ユーザーがアプリケーションの他の箇所に戻れるようにすることが考えられます。残念ながら、この手法も混乱を招く可能性があります。なぜなら、ユーザーがメイン ページに移動して "戻る" ボタンを押すと、アプリケーションが終了せず、固定されているレシピ ページに戻ることになるためです。このような理由から、"ホーム" ボタン メカニズムの実装は不可能ではありませんが、この動作については導入前に慎重に検討する必要があります。

XNA ゲームを組み込む

作業の開始時に、このアプリケーションを Windows Phone Silverlight/XNA アプリケーション ソリューションとして作成したことを思い出してください。この結果、3 つのプロジェクトが生成されました。ここまでは、メインの MangoApp プロジェクトを開発し、ゲーム以外の機能をビルドしました。GameLibrary プロジェクトは、Silverlight の MangoApp プロジェクトと XNA の GameContent プロジェクトの "橋渡し" として機能します。MangoApp プロジェクトからこのプロジェクトを参照する一方で、このプロジェクトから GameContent プロジェクトを参照します。橋渡しの機能に追加作業は必要ありません。ゲームを携帯電話アプリケーションに組み込むには、主に次の 2 つの作業が必要です。

  • すべてのゲーム ロジックを含むように MangoApp プロジェクトの GamePage クラスを強化する。
  • ゲームの画像と音声を使用できるように GameContent プロジェクトを強化する (コードの変更なし)。

Silverlight と XNA を統合するプロジェクト向けに Visual Studio によって行われた機能強化を簡単に説明すると、まず注目すべき点として、App.xaml で SharedGraphicsDeviceManager オブジェクトを宣言しています。このオブジェクトは、Silverlight と XNA の間での画面の共有を管理します。また、このオブジェクトはプロジェクトで AppServiceProvider クラスを追加する唯一の理由です。このクラスは共有グラフィック デバイス マネージャーのキャッシュに使用するため、Silverlight でも XNA でも、アプリケーションの任意の要素からこのクラスにアクセスできます。App クラスには AppServiceProvider フィールドを用意し、XNA を統合するための ContentManager と GameTimer という追加プロパティも公開しています。これらのフィールドとプロパティは、GameTimer と共にすべて新しい InitializeXnaApplication メソッドで初期化します。GameTimer は、XNA メッセージ キューからメッセージを取り出すために使用します。

おもしろいのは、XNA ゲームを Silverlight の携帯電話アプリケーションと統合する方法です。これに比べると、ゲーム自体はそれほどおもしろくありません。そのため、この演習ではゲーム全体をゼロから作成することに労力を費やすのではなく、既存のゲーム、具体的には AppHub の XNA ゲーム チュートリアル (bit.ly/h0ZM4o、英語) を応用します。

今回応用したバージョンでは、コードの Player クラスで表したカクテル シェーカーを用意し、近づいてくるマンゴー (敵) を標的に弾丸を発射します。マンゴーに命中すると、マンゴーがバラバラになってカクテルのマンゴティーニに変化します。1 つのマンゴーに命中するたびに、スコアが 100 点加算されます。カクテル シェーカーがマンゴーに衝突するたびに、プレーヤーのフィールド上の体力が 10 ポイントずつ減ります。体力が 0 になると、ゲームが終了します。また、ご想像のとおり、携帯電話の "戻る" ボタンを押せばいつでもゲームを終了できます。図 14 に、実行中のゲームを示します。

図 14 実行中の XNA ゲーム

(ほとんど空の) GamePage.xaml は、まったく変更する必要がありません。すべての処理は分離コードで実行します。Visual Studio によって、図 15 のような GamePage クラスのスターター コードが生成されます。

図 15 GamePage クラスのスターター コード

フィールド/メソッド 目的 必要な変更
ContentManager コンテンツ パイプラインからコンテンツの有効期間を読み込んで管理します。 このフィールドを使用して画像や音声を読み込むコードを追加します。
GameTimer XNA ゲーム モデルでは、Update イベントと Draw イベントの発生時にゲームの処理を実行し、これらのイベントをタイマーで管理します。 変更しません。
SpriteBatch XNA のテクスチャの描画に使用します。 Draw メソッドでこのオブジェクトを使用して、ゲーム オブジェクト (プレーヤー、敵、弾丸、爆発など) を描画するコードを追加します。
GamePage のコンストラクター タイマーを作成し、タイマーの Update イベントと Draw イベントを OnUpdate メソッドと OnDraw メソッドにフックします。 タイマー コードはそのままにし、追加でゲーム オブジェクトを初期化します。
OnNavigatedTo Silverlight と XNA の間でのグラフィックの共有を設定し、タイマーを開始します。 共有とタイマー コードはそのままにし、追加で分離ストレージから以前の状態など、コンテンツをゲームに読み込みます。
OnNavigatedFrom タイマーを停止して、XNA グラフィックの共有を無効にします。 共有とタイマー コードはそのままにし、追加でゲーム スコアとプレーヤーの体力を分離ストレージに格納します。
OnUpdate (空) GameTimer.Update イベントを処理します。 ゲーム オブジェクト 
(プレーヤーの位置、敵の数と位置、弾丸、爆発) の変化を計算するコードを追加します。
OnDraw (空) GameTimer.Draw イベントを処理します。 ゲーム オブジェクト、ゲーム スコア、およびプレーヤーの体力を描画するコードを追加します。

ゲームは AppHub のチュートリアルをそのまま応用したものです。このチュートリアルは、Shooter ゲーム プロジェクトと ShooterContent コンテンツ プロジェクトで構成されています。コンテンツには、画像と音声ファイルが含まれています。アプリケーション コードに影響はありませんが、アプリケーションのテーマであるマンゴーに合わせて画像と音声ファイルを変更でき、これは PNG ファイルと WAV ファイルを置き換えるだけの作業です。必要な (コード上の) 変更を、すべて Shooter ゲーム プロジェクトに加えます。Game クラスから Silverlight/XNA への移行に関するガイドラインについては、AppHub (create.msdn.com/ja-JP/education/catalog/article/migration_guide_moving_to_silverlight_xna) を参照してください。.

まず、Shooter ゲーム プロジェクトのファイルを既存の MangoApp プロジェクトにコピーします。また、ShooterContent コンテンツ ファイルを既存の GameContent プロジェクトにコピーします。図 16に、Shooter ゲーム プロジェクトに含まれる既存のクラスの概要を示します。

図 16 Shooter ゲーム プロジェクトのクラス

クラス 目的 必要な変更
Animation ゲーム内のさまざまなスプライト (プレーヤー、敵のオブジェクト、弾丸、および爆発) をアニメーションにします。 GameTime を削除します。
Enemy ユーザーが標的とする、敵のオブジェクトを表すスプライトです。応用バージョンでは、マンゴーを使用します。 GameTime を削除します。
Game1 ゲームの制御クラスです。 GamePage クラスにマージします。
ParallaxingBackground 雲の背景画像をアニメーションにして、3D の視差効果を提供します。 なし。
Player ユーザーのゲーム内でのキャラクターを表すスプライトです。応用バージョンでは、カクテル シェーカーを使用します。 GameTime を削除します。
Program ゲームが Windows または Xbox を対象とする場合にのみ使用します。 使用しないため、削除しても問題ありません。
Projectile ユーザーが敵に向かって発射する弾丸を表すスプライトです。 なし。

このゲームを携帯電話アプリケーションに組み込むには、GamePage クラスを次のように変更する必要があります。

  • Game1 クラスのすべてのフィールドを GamePage クラスにコピーします。また、Game1.Initialize メソッドに含まれるフィールドの初期化ロジックを GamePage のコンストラクターに追加します。
  • LoadContent メソッドと、敵、弾丸、および爆発を追加して更新するすべてのメソッドをコピーします。これらのメソッドを変更する必要はありません。
  • GraphicsDeviceManager の使用箇所をすべて抽出し、GraphicsDevice プロパティを代わりに使用します。
  • Game1.Update メソッドと Game1.Draw メソッドのコードを GamePage.OnUpdate タイマー イベント ハンドラーと GamePage.OnDraw タイマー イベント ハンドラーに抽出します。

通常の XNA ゲームでは新しい GraphicsDeviceManager を作成しますが、携帯電話アプリケーションでは、GraphicsDevice プロパティを公開する SharedGraphicsDeviceManager が既に存在するので、これ以外は特に必要ありません。処理を簡略化するために、GraphicsDevice への参照を GamePage クラスのフィールドとしてキャッシュします。

標準の XNA ゲームの Update メソッドと Draw メソッドは、Microsoft.Xna.Framework.Game 基本クラスに含まれる仮想メソッドのオーバーライドです。しかし、Silverlight/XNA 統合アプリケーションでは、GamePage クラスは XNA の Game クラスから派生していないため、Update メソッドと Draw メソッドのコードを抽出して、OnUpdate イベント ハンドラー メソッドと OnDraw イベント ハンドラー メソッドに挿入する必要があります。一部のゲーム オブジェクト クラス (Animation、Enemy、Player など)、Update メソッドと Draw メソッド、および Update メソッドから呼び出される一部のヘルパー メソッドが、GameTime パラメーターを受け取ります。このパラメーターは Microsoft.Xna.Framework.Game.dll アセンブリで定義されており、Silverlight アプリケーションにこのアセンブリへの参照が含まれている場合、通常はバグと見なされます。GameTime パラメーターは、2 つの Timespan 型プロパティ (TotalTime と ElapsedTime) に完全に置き換えることができます。これらのプロパティは、OnUpdate タイマー イベント ハンドラーと OnDraw タイマー イベント ハンドラーに渡す GameTimerEventArgs オブジェクトから公開されます。GameTime パラメーター以外については、Draw メソッドのコードを変更せずに移植できます。

元の Update メソッドでは、GamePad の状態をテストして、条件に応じて Game.Exit を呼び出します。Silverlight/XNA 統合アプリケーションではこのロジックを使用しないため、次のようにコメントアウトして、新しいメソッドに移植しないようにします。

//if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
//{
//    // this.Exit();
//}

これで新しい Update メソッドは、他のメソッドを呼び出してさまざまなゲーム オブジェクトを更新するためのハーネスにすぎなくなります。ゲーム オーバーになったときも視差効果を付けた背景を更新しますが、プレーヤー、敵、衝突、および弾丸については、プレーヤーの体力がまだ残っている場合にのみ更新します。呼び出すヘルパー メソッドでは、さまざまなゲーム オブジェクトの数と位置を計算します。GameTime を使用する箇所を削除しているため、次のように、これらのメソッドを変更せずに移植できます。ただし、例外が 1 つあります。

private void OnUpdate(object sender, GameTimerEventArgs e)
{
  backgroundLayer1.Update();
  backgroundLayer2.Update();
 
  if (isPlayerAlive)
  {
    UpdatePlayer(e.TotalTime, e.ElapsedTime);
    UpdateEnemies(e.TotalTime, e.ElapsedTime);
    UpdateCollision();
    UpdateProjectiles();
    UpdateExplosions(e.TotalTime, e.ElapsedTime);
  }
}

UpdatePlayer メソッドについては微調整が必要です。ゲームの元のバージョンでは、プレーヤーの体力が 0 になったら、その体力を 100 にリセットしています。つまり、ゲームは無限にループします。今回応用したバージョンでは、プレーヤーの体力が 0 になったら、次のようにフラグを false に設定します。このフラグは、OnUpdate メソッドと OnDraw メソッドの両方でテストします。OnUpdate メソッドでは、フラグの値を使用して、オブジェクトへの変更を計算し続けるかどうか判断します。OnDraw メソッドでは、オブジェクトを描画するか、最終スコアを表示した "ゲーム オーバー" 画面を描画するか判断します。

private void UpdatePlayer(TimeSpan totalTime, TimeSpan elapsedTime)
{
...unchanged code omitted for brevity.
 
  if (player.Health <= 0)
  {
    //player.Health = 100;
    //score = 0;
    gameOverSound.Play();
    isPlayerAlive = false;
  }
}

開発を楽しむ

今回は、ローカル データベース、LINQ to SQL、セカンダリ タイルとディープ リンクの設定、Silverlight/XNA の統合など、いくつかの Windows Phone SDK 7.1 の新機能を対象とするアプリケーションの開発方法について説明しました。7.1 リリースには、新機能や既存機能の強化がほかにも多数あります。詳細については、次のリンクを参照してください。

  • 「Windows Phone SDK の新機能」(bit.ly/c2RmNr、英語)
  • 「Windows Phone 用タイルの概要」(bit.ly/oQlu15、英語)
  • 「方法: Windows Phone アプリケーションで Silverlight と XNA Framework を組み合わせる」(bit.ly/p4RncQ、英語)
  • 「Windows Phone のローカル データベースの概要」(bit.ly/l23UQM、英語)

Mangolicious アプリケーションの最終バージョンは、Windows Phone Marketplace (bit.ly/nuJcTA、英語) から入手できます (注: アクセスするには Zune ソフトウェアが必要です)。このサンプルでは、Silverlight for Windows Phone Toolkit を使用している点に注意してください (bit.ly/qiHnTT (英語) から無料でダウンロードできます)。

Andrew Whitechapel は、20 年以上にわたって開発に携わっており、現在は Windows Phone チームのプログラム マネージャーとしてアプリケーション プラットフォームの中核部分を担当しています。

この記事のレビューに協力してくれた技術スタッフの Nick GravelynBrian Hudson、および Himadri Sarkar に心より感謝いたします。