WisdomSoft - for your serial experiences.

コンテンツ管理

コンテンツプロジェクトにファイルを追加し、ゲームで読み込むアセットを事前コンパイルする方法を紹介します。

コンテンツの設定

FromStream() メソッドによるファイルからテクスチャを読み込む方法は便利ですが、ファイルにはゲームに不要な情報が含まれています。ゲームで用いるリソースには、ファイル管理のための情報(メタデータ)は不要です。そこで、XNA Game Studio では、ビルド時にファイルをバイナリ形式のファイルに変換し最適化するコンテンツパイプラインと呼ばれる仕組みを提供します。

コンテンツパイプラインは、画像などゲームで使われるデータファイルをビルド時に読み込み、これをアセットと呼ばれるファイルに変換します。アセットは、元のファイルの形式とは独立して、直接オブジェクト化可能な直列化されたバイナリ形式のファイルです。よって、ゲーム実行時にファイルからオブジェクト型への変換処理が発生しません。 

図1 コンテンツ処理の概念
図1 コンテンツ処理の概念

コンテンツパイプラインで扱えるファイルは、画像ファイルだけではありません。3D モデルやフォント、XML ファイルなども、コンテンツとして登録することで、事前に変換できます。この場では、画像ファイルを登録して読み込んでみましょう。

XNA Game Studio でプロジェクトを作成したとき、ゲーム用のプロジェクトに加えて「プロジェクト名 + Content」という名前のプロジェクトが生成されていました。例えば、プロジェクト名が「TestGame」という名前であれば「TestGameContent」という名前のプロジェクトが生成されています。これをコンテンツプロジェクトと呼び、コンテンツパイプラインに処理されるファイルはコンテンツプロジェクトに登録します。

コンテンツパイプラインにファイルを登録するには XNA Game Studio のプロジェクト内にあるコンテンツプロジェクトを選択している状態で、右クリックで表示されたポップアップメニューから「追加」→「既存の項目」を選択してください。、またはメニューバーの「プロジェクト」を選択し、表示された「既存項目の追加」を選択します。ファイル選択ダイアログボックスが表示されるので、BMP や JPEG など、XNA Framework が対応している画像ファイルを追加します。

図2 コンテンツの追加
図2 コンテンツの追加

ファイルを登録すると、コンテンツプロジェクトに追加したファイルが表示されます。コンテンツプロジェクトはゲームプロジェクトからは独立しているため、Windows、Xbox 360、Windows Phone など、異なるプラットフォームの複数のプロジェクトで共有できます。クロスプラットフォームのゲームを開発する場合でも、コンテンツを容易に管理できます。

コンテンツプロジェクトに追加したファイルを選択して、プロパティウィンドウを表示してください。図3のようなプロパティが列挙されます。

図3 コンテンツのプロパティ
図3 コンテンツのプロパティ

この中でも、生成されるアセットの名前を表す「アセット名」プロパティはコンテンツの読み込みに必要となる名前なので、最も重要となります。「アセット名」プロパティに、分かりやすい名前をつけてください。コンテンツが見つからない場合は、実行時に指定しているコンテンツの名前と「アセット名」プロパティの値が一致しているかどうかを調べてください。

その他のプロパティを変更する必要はありません。「コンテンツ インポーター」プロパティは、コンテンツを読み込むプログラムを設定しています。これはインポータと呼ばれる処理で、ビルド時にコンテンツを読み込むため必要な設定です。「コンテンツ プロセッサ」プロパティは、読み込んだコンテンツを変換するプロセッサと呼ばれるプログラムの設定です。これらの働きによって画像ファイルが適切な形に置き換えられ、結果としてアセットが出力されます。今回は、テクスチャを処理するので、どちらのプロパティにも「テクスチャー : XNA Framework」を設定してください。

コンテンツプロジェクトにファイルを追加している状態で、ゲームプロジェクトを正常にビルドすることができれば、出力先のフォルダに Content フォルダが作られ、その中に生成されたアセットが含まれています。アセットの出力先は、コンテンツプロジェクトのプロパティから変更できます。アセットは、プロパティで設定したアセット名に XNB という拡張子が付いたファイルです。

図4 生成された XNB ファイル
図4 生成された XNB ファイル

図4は、生成されたアセットファイルです。このファイルをゲームから読み込むことができます。ビルド時にテクスチャとしてデータが変換されているため、画像ファイルから読み込むよりも効率的です。

コンテンツの読み込み

アセットをゲーム実行時に読み込むには Microsoft.Xna.Framework.Content.ContentManager クラスを使います。ゲーム開発者は、アセットのフォーマットがどのようになっているかを理解する必要はありません。読み込むアセットの名前を指定するだけで、目的のオブジェクトを取得できます。

Microsoft.Xna.Framework.Content.ContentManager クラス
public class ContentManager : IDisposable

このクラスのコンストラクタは、次のようなものがあります。

ContentManager クラスのコンストラクタ
public ContentManager (
         IServiceProvider serviceProvider
)
public ContentManager (
         IServiceProvider serviceProvider,
         string rootDirectory
)

serviceProvider パラメータには、サービスを検出するためのサービスプロバイダを表す IServiceProvider 型のオブジェクトを指定します。IServiceProvider 型は System 名前空間で宣言されているインターフェイスで、複数のインターフェイスの実装を管理する機能を提供します。rootDirectory には、コンテンツを検索するディレクトリのパスを指定します。このパスを省略した場合は、実行可能ファイルが配置されているディレクトリが検索されます。

ContentManager は、テクスチャなどのインスタンス化に GraphicsDevice オブジェクトを必要とするため、GraphicsDeviceManager を検索するためのサービスプロバイダを要求します。サービスの詳細については後述するので、この場では Game クラスの Services プロパティが返すオブジェクトを渡してください。

コンテンツの検索ディレクトリは、デフォルトでは実行ファイルと同じディレクトリが対象となります。XNA Gmae Studio 2.0 以降は、コンテンツを Content ディレクトリ内に配置されるようになったため、通常は "Content" を検索ディレクトリに指定します。この設定は RootDirectory プロパティからも変更できます。

ContentManager クラス RootDirectory プロパティ
public string RootDirectory { get; set; }

ContentManager オブジェクトを用意できれば、LoadContent() メソッドなどが呼び出されたタイミングで Load() メソッドを呼び出してコンテンツを読み込みます。

ContentManager クラス Load<T>() メソッド
public virtual T Load<T> (string assetName)

型パラメータ T には、読み込むアセットの型を指定します。画像ファイルからテクスチャを取得する場合は Texture2D を指定します。メソッドは、アセットを読み込み T 型としてオブジェクトを返します。T 型に変換できない場合は、例外が発生します。このように Load() メソッドは、読み込むデータ型を型パラメータで指定できるので、フォントや 3D モデルなども同じ方法で取得できます。

読み込んだコンテンツが不要になれば Unload() メソッドで破棄できます。 

ContentManager クラス Unload() メソッド
public virtual void Unload ()

このメソッドを実行すると、ContentManager が読み込んだコンテンツをすべて破棄します。UnloadContent() メソッドなど、Load() メソッドでコンテンツを読み込んだ処理と対になる解放処理で使えます。

コード1
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

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

    private GraphicsDeviceManager graphicsDeviceManager;
    private ContentManager contentManager;
    private SpriteBatch sprite;
    private Texture2D texture;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        contentManager = new ContentManager(Services, "Content");
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        texture = contentManager.Load<Texture2D>("TestImage");

        base.LoadContent();
    }

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

        sprite.Begin();
        sprite.Draw(texture, Vector2.Zero, Color.White);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は、事前にコンテンツとして登録した画像ファイルをビルド時にアセットに変換し、実行時に ContentManager を通してアセットからテクスチャを読み込んでいます。テクスチャを取得してからの描画方法は、ファイルから読み込んだ場合と同じです。重要なのはストリームからではなく、ContentManager クラスの Load() メソッドを通して、テクスチャを読み込んでいる部分です。

ContentManager を使ったコンテンツの管理はストリームからテクスチャを読み込む方法に比べて、多くのメリットがあります。テクスチャや、3D モデルなど、型の異なるコンテンツを ContentManager で統合管理でき、ファイル管理が不要になります。コンテンツは、コンテンツプロジェクトによって管理されるため、他のプラットフォームにゲームを移植する場合でも共有できます。一方のプロジェクトのコンテンツだけが更新されず古いままだったために生じる不整合などのミスを避けられます。

加えて、ゲーム独自のデータ型も、コンテンツパイプラインを拡張することで対応できます。インポータやプロセッサを開発し、プロジェクトに組み込むことで標準でサポートされていないファイルや独自のデータ型をコンテンツパイプラインに組み込めます。カジュアルなゲームであれば、最初から用意されている機能で十分ですが、大規模なゲームを開発であれば、独自にコンテンツパイプラインを拡張する機会もあるでしょう。

用意された ContentManager

コード1のように、ContentManager クラスのコンストラクタからインスタンスを生成することも可能ですが、Game クラスが提供する Cotent プロパティから ContentManager を取得することもできます。Game クラス内で使われるコンテンツの読み込みには Content プロパティが返すオブジェクトを使うと便利です。

Game クラス Content プロパティ
public ContentManager Content { get; }

このプロパティが返す ContentManager は、Game クラスのインスタンスが生成された時点で有効になっていますが、GraphicsDevice が生成されるまでは Load() メソッドでコンテンツを読み込むことはできません。

コード2
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

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

    private GraphicsDeviceManager graphicsDeviceManager;
    private SpriteBatch sprite;
    private Texture2D texture;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        texture = Content.Load<Texture2D>("TestImage");

        base.LoadContent();
    }

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

        sprite.Begin();
        sprite.Draw(texture, Vector2.Zero, Color.White);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果
コード2 実行結果

コード2の結果は、アセットを読み込むための ContentManager を Game クラスの Content プロパティから取得している点を除いてコード1と同じです。ContentManager のインスタンスの生成や、フィールドへの保存などが不要になり、わずかですが作業を減らせます。このプロパティは XNA Framework 2.0 で追加されたもので、XNA Framework 1.0 ではコード1の方法しかありませんでした。