8.2 インポータ
8.2.1 ファイルを読み込む
コンテンツパイプラインによるソースアセットのビルドは、インポータによって読み込まれ処理用のデータに変換されることから始まります。インポータの役割は、ビルド時に指定されたファイルを読み込んで結果をプロセッサに渡すことです。画像といった同じ性質のデータでも BMP や PNG などいくつかの種類があり、これらのデータを読み込む方法はファイル形式によって異なります。このようなファイル形式の違いはインポータによって吸収されます。
インポータの役割によって、データを処理するプロセッサをファイル形式ごとに作成する必要がなくなります。例えば、XNA Framework でサポートされていない画像ファイルを Texture2D 型のオブジェクトとしてゲームで利用するには、専用のインポータのみを開発してプロセッサは TextureProcessor を流用できます。3Dモデルの読みこみで FbxImporter 及び XImpoter と ModelProcessor の関係がこれに相当します。ModelProcessor にとって、元の 3D モデルのファイル形式が何であるかは重要ではありません。ファイルからのデータの読み込みは、ファイル形式の応じて専用のインポータが行ってくれています。
新たにインポータを開発するには、Microsoft.Xna.Framework.Content.Pipeline.ContentImporter クラスを継承する独自のクラスを作成します。
public abstract class ContentImporter<T> : IContentImporter
このクラスの T 型パラメータには、インポータの出力型を指定します。
ContentImporter クラスは抽象クラスであり、このクラスを継承するクラスはImport()メソッドを実装しなければなりません。Import() メソッドは ContentImporter クラスで宣言されている抽象メソッドで、指定されたファイル名からデータを読み込み、適切なT型の結果を出力しなければなりません。このメソッドをプログラマが明示的に呼び出す必要はなく、ビルド時に自動的に呼び出されます。
public abstract T Import ( string filename, ContentImporterContext context )
filename パラメータには、インポータが読み込むファイル名が指定されます。context パラメータは、インポータの実行処理に関する情報を提供する Microsoft.Xna.Framework.Content.Pipeline.ContentImporterContext クラスのオブジェクトが渡されます。
public sealed class ContentImporterContext
インポータやプロセッサなど、コンテンツパイプラインはアプリケーションとして実行されるわけではないので、通常のアプリケーションのようにブレークポイントを設定して実行を停止することができません。CLR デバッガ(DbgCLR.exe)を使うなど、専用のデバッグツールを使う方法もありますが、標準でサポートされていないツールを使うことは敷居が高く面倒と感じてしまうかもしれません。
変数の値を確認する程度であれば Visual Studio の「出力」ウィンドウに任意のメッセージを出力できるので、これを利用すると便利です。メッセージを出力するには Logger プロパティを使います。
public ContentBuildLogger Logger { get; }
このプロパティは、インポータやプロセッサの処理過程のメッセージや、警告やエラーを報告する機能を提供する Microsoft.Xna.Framework.Content.Pipeline.ContentBuildLogger クラスのオブジェクトを返します。
public abstract class ContentBuildLogger
このクラスは、優先順位の低い一般的なメッセージを出力する LogMessage() メソッドと、より優先順位の高い警告文を出力する LogImpotantMessage() メソッドを提供しています。デフォルトの Visual Studio の設定では、LogMessage() メソッドによるメッセージは「出力」ウィンドウには表示されません。一方 LogImportantMessage() メソッドのメッセージは「出力」ウィンドウに表示されます。
public abstract void LogMessage ( string message, Object[] messageArgs )
public abstract void LogImportantMessage ( string message, Object[] messageArgs )
message パラメータに出力するテキストを表す書式指定文字列を、messageArgs には message パラメータに指定した文字列に対するパラメータを任意の数だけ指定できます。これらのメソッドの使い方は、文字列とパラメータを受ける WriteLine() メソッドと基本的に同じです。
LogMessage() メソッドのよる標準のメッセージを表示させたい場合には「ツール」メニューの「オプション」項目を選択して「オプション」ダイアログを表示し、「MSBuild プロジェクト ビルドの詳細」の値を「標準」に変更してください。デフォルトでは「最小」に設定されています。
最後に、ContentImporter クラスを継承した独自のインポータに対して Microsoft.Xna.Framework.Content.Pipeline.ContentImporterAttribute クラスを属性として設定します。
public class ContentImporterAttribute : Attribute
この属性は、インポータがサポートするファイルの拡張子やインポータの名前などの情報を提供します。コンストラクタには文字列、または文字列の配列でインポータが対応する拡張子を指定します。
public ContentImporterAttribute ( string fileExtension )
public ContentImporterAttribute ( string[] fileExtensions )
fileExtension パラメータにはインポータが読み込むことのできるファイルの拡張子を指定します。fileExtensions パラメータは、任意の数のパラメータで対応するファイル拡張子のリストを指定できます。拡張子の名前は必ず . 記号から始まり ".txt" や ".bmp" という形で指定します。インポータが複数のファイル形式に対応してもかまいません。
また DisplayName プロパティに、開発環境に表示されるインポータの名前を設定できます。
public virtual string DisplayName { get; set; }
このプロパティに設定されている文字列が、インポータの名前として「プロパティ」ウィンドウに表示されます。
これで、ContentImporter クラスを継承した独自のインポータが XNA Game Studio に認識されるようになります。
using System.IO; using Microsoft.Xna.Framework.Content.Pipeline; [ContentImporter(".txt", DisplayName = "Text Importer")] public class TextImporter : ContentImporter<string> { public override string Import(string filename, ContentImporterContext context) { FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read); StreamReader reader = new StreamReader(stream); string result = ""; while (!reader.EndOfStream) result += reader.ReadLine(); context.Logger.LogMessage("通常のメッセージです 入力ファイル=" + filename); context.Logger.LogImportantMessage("重要なメッセージです 入力文字数={0:D}", result.Length); return result; } }
コード1の ContentPipelineExtension プロジェクトは、コンテンツパイプラインの拡張ライブラリを作成するためのプロジェクトです。このプロジェクトに、上記のコードを記述してビルドすると出力フォルダにDLLファイルが生成されています。このライブラリに、記述したインポータが含まれています。
このプログラムでは、純粋なテキストファイル(.txt)を読み込み文字列型のオブジェクトとして返す TextImporter クラスを作成しています。インポータに設定している ContentImporterAttribute 属性で、インポータが読み込むファイルの拡張子が .txt であることを開発環境に伝えています。また、DisplayName プロパティから表示用のインポータ名を設定しています。
ゲーム用プロジェクトの Content サブプロジェクトにテキストファイルを追加し、テキストファイルを選択した状態で「プロパティ」ウィンドウの「Content Impoter」プロパティの値が「Text Impoter」に設定されていることを確認してください。ここで表示されるインポータの名前が、DisplayName プロパティに設定した文字列になります。
プロセッサの開発については後述するので、この場では「Content Processor」プロパティに変換を行わない「No processing Required」を選択してください。プロセッサが選択されていない状態でも同じです。「No processing Required」が選択されていると、インポータが返したデータがそのままコンパイル済みアセットとして出力されます。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; public class Test : Game { public static void Main(string[] args) { using (Game game = new Test()) game.Run(); } private GraphicsDeviceManager graphicsDeviceManager; private SpriteBatch sprite; private SpriteFont font; private string text; public Test() { graphicsDeviceManager = new GraphicsDeviceManager(this); } protected override void LoadContent() { sprite = new SpriteBatch(GraphicsDevice); font = Content.Load<SpriteFont>("Content/TestFont"); text = Content.Load<string>("Content/TestText"); base.LoadContent(); } 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); } }
コード2の Game プロジェクトは、前述したインポータを利用してコンパイルされた文字列を読み込んで描画するゲームです。コンテンツパイプラインの事前処理のおかげで、ゲームではテキストファイルを実行時に読み込んで解析するといった面倒な作業が省略され、単純にアセット名を指定するだけテキストファイルの文字列を取得できます。
このように、インポータを独自に開発することでXNA Frameworkが標準でサポートしていない特殊なデータや、他のゲームエンジン向けに作られたデータをXNA Frameworkゲーム用に読み込むことができます。これによって、ゲームとコンテンツが互いに依存することなく独立し、デザイナは自分が好むツールでコンテンツを作成して、ビルド時にコンテンツパイプラインで適切な変換を行ってゲームと統合できます。小規模な開発では不要だと思われますが、組織的な開発には不可欠な要素となります。