8.4 書き込みと読み込み
8.4.1 タイプライタの作成
これまでは.NET Frameworkの基本型である文字列を返してコンパイル済みアセットに出力し、実行時に読み込んでいました。このとき、プロセッサが返したデータをファイルに書き込む処理はタイプライタに、ゲーム実行時の読み込みはタイプリーダと呼ばれるプログラムによって自動的に行われていました。ContentManagerクラスのLoad()メソッドは、内部で適切なタイプリーダを使ってアセットを読み込み、オブジェクト化してゲームに結果を返しているのです。
当然、独自に作成したクラスなどコンテンツパイプラインが標準でサポートしていないデータが自動的に読み書きされることはありません。プロセッサが返した結果に対応するタイプライタが見つからない場合、ビルドは失敗します。この問題を解決するには、独自のデータ型を処理するタイプライトとタイプリーダを記述し、コンテンツパイプラインに組み込む必要があります。
タイプライタは、インポータやプロセッサと同じコンテンツパイプラインの拡張ライブラリ用のプロジェクトに記述します。新しいタイプライタを作成するには Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler.ContentTypeWriter クラスを継承します。抽象クラスを継承し、必要なメソッドを実装するという工程もインポータやプロセッサと同じです。ContentTypeWriterクラスはobject型で書き込むデータを扱いますが、このクラスを継承する同じ名前のジェネリッククラスが用意されています。通常、このジェネリッククラスを継承します。
public abstract class ContentTypeWriter<T> : ContentTypeWriter
T 型パラメータには、このタイプライタで書き込む型を指定します。
このクラスを継承するクラスは、オブジェクトを書き込む Write() メソッドを実装しなければなりません。
protected internal abstract void Write ( ContentWriter output, T value )
output パラメータには、オブジェクトを書き込むためのメソッドを提供する Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler.ContentWriter クラスのオブジェクトが渡されます。value パラメータには、プロセッサが出力した書き込むオブジェクトが渡されます。
タイプライタの開発者は、output パラメータに渡された ContentWriter オブジェクトを通してアセットにオブジェクトを書き込まなければなりません。
public sealed class ContentWriter : BinaryWriter
このクラスは System.IO 名前空間の BinaryWriter クラスを継承し、.NET Framework の基本データ型に加えて XNA Framework の Vector2 構造体や Color 構造体などの基本データ型の書き込みをサポートするオーバーロードされた Write() メソッドを提供します。そのため、バイナリファイルを出力する場合と同じようにアセットを書き込めます。
ContentWriter クラスは、ビルド対象のプラットフォームを TargetPlatform プロパティで提供しています。このプロパティを調べることで、データの書き込み処理を Windows 用や Xbox360 用など、特定のプラットフォームごとに切り替えることができます。
public TargetPlatform TargetPlatform { get; }
targetPlatform プロパティには、現在のビルドの対象プラットフォームを表す TargetPlatform 列挙型のメンバが渡されます。プラットフォームによって使用するタイプリーダを切り替える場合に、このパラメータの値を使います。メソッドは、タイプリーダのアセンブリ修飾子を表す文字列を返します。
アセンブリ修飾子は、名前空間、アセンブリ名、バージョンやカルチャなどを含む完全な型の名前を文字列で表現したものです。通常は、Type クラスの AssemblyQualifiedName プロパティから取得できます。
最後に、ContentTypeWriter クラスを継承する新しいタイプライタに Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler.ContentTypeWriterAttribute クラスを属性として設定してください。
[AttributeUsageAttribute(4)] public sealed class ContentTypeWriterAttribute : Attribute
このクラスのコンストラクタは、特にパラメータを受け取りません。
この場では、ゲーム画面に描画する文字列に加えて、色と座標を含めたTextElementクラスを新たに作成しましょう。これをタイプライタでコンパイル済みアセットに書き込みます。インポータでテキストファイルから文字列を読み込み、プロセッサのパラメータからテキストの色や座標を追加してTextElementオブジェクトを初期化します。プロセッサはTextElementオブジェクトを結果として返し、これをタイプライタで書き込みます。
新しく作成するTextElementクラスは、ゲーム用とコンテンツパイプラインの両方から参照できなければなりません。TextElementのようなゲームとコンテンツパイプラインのプロジェクトとは別に、ゲーム用のライブラリを作成する新しいプロジェクトを作成します。ゲーム用のライブラリは「新しいプロジェクト」ダイアログの「Windows Game Library (3.0)」を選択してください。
ここで作成したプロジェクトを、ゲーム用のプロジェクトとコンテンツパイプラインの拡張ライブラリ用プロジェクトの両方から参照します。
ゲームライブラリには、複数の異なるゲームやコンテンツパイプラインの処理などで共通して利用される機能を提供します。このプロジェクト内に TextElement クラスを作成してください。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; public class TextElement { private string text; private Color color; private Vector2 position; public string Text { get { return text; } set { text = value; } } public Color Color { get { return color; } set { color = value; } } public Vector2 Position { get { return position; } set { position = value; } } }
コード1の GameLibrary プロジェクトにある TextElement クラスは、描画する文字列を表す Text プロパティに加えて、テキストの色を表す Color プロパティ、座標を表す Position プロパティを公開しています。コンテンツパイプラインでテキストファイルから TextElement オブジェクトを生成し、ゲームでこれを読み込みます。
TextElement クラスの Text プロパティにはインポータから渡された文字列を設定するものとし、他のプロパティの値はプロセッサのパラメータから受け取るものとします。
using System.ComponentModel; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content.Pipeline; [ContentProcessor(DisplayName = "Text Processor")] public class TextProcessor : ContentProcessor<string, TextElement> { private Color color; private Vector2 position; [Description("テキストの色です")] public Color Color { get { return color; } set { color = value; } } [Description("テキストを描画する座標です")] public Vector2 Position { get { return position; } set { position = value; } } public override TextElement Process(string input, ContentProcessorContext context) { TextElement element = new TextElement(); element.Text = input; element.Color = color; element.Position = position; return element; } }
ContentPipelineExtension プロジェクト内の TextProcessor クラスは、文字列を受け取り TextElement オブジェクトを出力するプロセッサです。XNA Framework が標準で提供するタイプライタでは TextElement を処理できないので、このままではビルドエラーになってしまうでしょう。そこで、TextElement オブジェクトを書き込む新しいタイプライタを作成します。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; [ContentTypeWriter] public class TextElementTypeWriter : ContentTypeWriter<TextElement> { public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(TextElementTypeReader).AssemblyQualifiedName; } protected override void Write(ContentWriter output, TextElement value) { output.Write(value.Text); output.Write(value.Color); output.Write(value.Position); } }
TextElementTypeWriter クラスは、ContentTypeWriter クラスを継承する新しいタイプライタです。このクラスの Write() メソッドで、プロセッサが返した TextElement オブジェクトを受け取りアセットに書き込みます。どのようにオブジェクトを直列化するかは自由ですが、この場では最初に文字列を書き込み、続いて色、座標を書き込んでいます。対応するタイプライタは、この順番でデータを読み込まなければなりません。
GetRuntimeReader() メソッドでは、このタイプライタに対応する TextElement オブジェクトを読み込むためのタイプリーダのアセンブリ修飾子を返しています。タイプリーダは TextElementTypeReader クラスとして実装するものとします。
8.4.2 タイプリーダの作成
タイプリーダは、タイプライタとは異なるプロジェクトとして用意する必要があります。タイプライタは、インポータやプロセッサと同じようにゲームのコンテンツをビルドするときに開発環境によって実行されます。しかし、タイプリーダはゲームの実行時に呼び出されます。この場では、TextElement クラスと同じようにゲームライブラリ用のプロジェクトにタイプリーダを記述しましょう。
タイプリーダを作成するには ContentTypeReader クラスを継承します。基本的な手順はタイプライタと同じですが、このクラスはゲームから利用されるため Microsoft.Xna.Framework.Content 名前空間内にあります。ContentTypeWriter クラスとは別の名前空間なので注意してください。
ContentTypeReader クラスは読み込むデータを object 型として扱うクラスですが、タイプライタと同様に同じ名前のジェネリッククラスが用意されているので、通常はこちらを使います。
public abstract class ContentTypeReader<T> : ContentTypeReader
T 型パラメータには、このタイプリーダが読み込む型を指定します。
このクラスを継承するクラスは Read() メソッドを実装しなければなりません。このメソッドは、コンパイル済みアセットからデータを読み込み、適切なオブジェクトの復元して結果を返さなければなりません。
protected internal abstract T Read ( ContentReader input, T existingInstance )
input パラメータには、アセットからデータを読み込むための Microsoft.Xna.Framework.Content.ContentReader クラスのオブジェクトが渡されます。existingInstance パラメータはデータを受け取るオブジェクトが渡されますが、このパラメータが null の場合は新しいインスタンスを生成して結果を返します。
ContentReader クラスは、タイプライタで用いた ContentWriter クラスと対になる読み込み機能を提供します。このクラス BinaryReader クラスから派生しており、多くのデータ型の読み込みを Read*() メソッドでサポートします。例えば、文字列の読み込みは ReadString() メソッド、32ビット整数の読み込みは ReadInt32() メソッドを使います。これらのメソッドは BinaryReader クラスで提供されているものです。
public sealed class ContentReader : BinaryReader
加えて、XNA Frameworkの基本的な型であるColor構造体やVector2構造体を読み込むReadColor()メソッドやReadVector2()メソッドなどが提供されています。これらのメソッドを使って、タイプライタで書き込んだデータを読み込み、元のオブジェクトに復元します。
アセットを読み込むタイプリーダは、アセットを書き込んだタイプライタのGetRuntimeType()メソッドが返したアセンブリ修飾子によって指定されるため、タイプリーダであることを表す属性を設定する必要はありません。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; public class TextElementTypeReader : ContentTypeReader<TextElement> { protected override TextElement Read(ContentReader input, TextElement existingInstance) { TextElement textElement; if (existingInstance == null) textElement = new TextElement(); else textElement = existingInstance; textElement.Text = input.ReadString(); textElement.Color = input.ReadColor(); textElement.Position = input.ReadVector2(); return textElement; } }
GameLibrary プロジェクトの TextElementTypeReader クラスは、前述した TextElementTypeWriter クラスで書き込んだ TextElement オブジェクトを読み込むタイプリーダです。TextElementTypeWriter クラスの GetRuntimeReader() メソッドで、アセットを読み込むためのタイプリーダとして TextElementTypeReader クラスのアセンブリ修飾子を返しています。ゲーム実行時に ContentManager クラスの Load() メソッドでアセットを指定すると、自動的に TextElementTypeReader が呼び出されます。
コード4のゲームを実行すると、テキストファイルから生成した TextElement オブジェクトを正しく受け取れることを確認できます。テキストの色と座標は、プロセッサのパラメータから自由に変更できます。