背景の情報管理について (モデルから座標情報の取得について)
※これは2007/4月の頃のお話です。
XNAで3Dのアクションゲームを作成するにあたり、背景のモデリングとその背景の座標情報をどのように設計するか検討しました。
基本的にキャラクターも背景も3Dのモデリングで作成する訳なので、ツールはMetasequoiaを使用しようと考えた訳なのですが、「背景の座標情報をどのようにして取得するのだろう?」と考えたところ、以下のアプローチが思い浮びました。
【モデルクラスの中を覗いて取得する】
まず普通に考えれば、XNAでモデルファイル(Xファイル)の情報が読み込め、これがModelクラスに格納され、これをforeachでメッシュ単位で描画まで行えているのだから、ここに座標情報が無いのはおかしいと思い、C#のコンパイラのメンバ表示で中をどんどん掘り下げていったところ、おおまかな階層でいうと、
Model.Meshes[配列].ModelMesh.VertexBuffer.GetData
というメソッドがあったので(ちなみにこれはジェネリクスで型定義がされていました)、「よし、これだっ!」という感じでDirect3Dで定義されているような型をXNA上で探し、これをジェネリクスタイプとして定義して呼び出したところ、エクセプションが走りました…(^^;
「あれれ? "GetData"なのになんでだろ? 型が不正なのかな?」と思い、エクセプションの内容を見ると「WriteOnly」…
「えぇ~? 型が不正というよりも、"GetData"なのに"書き込み専用"って、何???」みたいな状況となりました。
とりあえず、モデルの頂点情報が取得できないのであれば、背景のヒット判定ができないので、次の手法を検討しました。
【コンテントパイプラインを実装して取得する】
上述の方法がシンプルとは思いましたが、よく分りませんがメソッドがWriteOnlyですし、当初のXNAアーキテクチャーではこの方法がセオリーっぽく感じたので、これを実装することに…
とりあえず新しいプロジェクトとして、背景用のコンテントパイプラインを作成して、現在のソリューションに追加しました。
…と簡単に言いましたが、コンテントパイプラインの実装には本当に骨が折れました。
まずなにより「情報が殆んどない!」という流れから始まり、全世界をネットでかけずり周りながら、以下の順番で障壁にブツかりまくりました。
(『趣味』にしてはかなりキツイ作業としかいいようがない位…)
1.エントリポイントの作り方
とりあえずプロジェクト名を"BGContent"とした、クラスライブラリとしてプロジェクトを作成し、現在のソリューションに加えました。
そして、ModelProcessorクラスから派生させたBackgroundModelProcessorというクラスを実装し始めました。
このクラスにエントリポイント(正確にはコールバック)となるProcessというメソッドをオーバーライドして、そのメソッドの最初に基底クラスのメソッドを呼び出してみて、既存のモデルがコンテントパイプラインとして機能するか実験してみました。
とりあえずまずは成功したっぽかったので、これを登録した背景となるモデルのプロパティの"Content Processor"に登録してみて、この背景のモデルが読み込まれるか実験してみました。
ちゃんとコンテントプロセッサができなければXファイルのコンパイルの時点でエラーとなります。(なりました(^^;)

ここでポイントなのは戻り値が普段扱わないModelContentクラスというのも驚きました。「なんだこのクラス、見たことない!」と、まるでレアアイテムのようなノリで調べても、ネットにも情報がない…という始末でした。
2.頂点情報の取得 …その前に
ModelContentというクラスが取得できたので、この中を例の如くコンパイラのメンバ表示で覗いてみると、MeshesやらBonesやらとお宝情報が見えてきたので、なんとかしてコイツの情報を取得したいと情報を求めてネット上でアメリカ辺りをウロついていたら、この情報を取得する方法について説明があるのを発見しました。
これを読むと「ん? ContentTypeReader、ContentTypeWriterが必要…?」、よくよく考えると、コンテントパイプラインというのはモデル等のコンテンツ情報を読んで、Windowsや、Xbox 360のプラットフォームに合せて変換して出力する…という流れのためのものなので、リーダーやライターがあって当然…
ということで、これの実装を試みました。
とりあえず現状はWindowsのみで進める予定なので、エンディアンとか気にしないで、そのまま頂点、頂点インデックス、テクスチャーファイル名の情報を入出力することにしました。
この入出力の実装により、コンパイル時にコンテンツのコンパイル(変換)が行われ、実行ファイルと共に変換されたコンテンツが出力される寸法です。(多分…)
…が、しかし…
3.モデル情報を格納する型を決める
ここで情報のI/Oのために自前でフォーマットを用意するようで、頂点情報のバッファ、頂点インデックスバッファ、テクスチャーファイル名のバッファ(これは将来属性でもいいかな…と)を内包したクラスを作成し、これをReader, Writerのジェネリクスタイプにすることにしました。
また、Writerでは、このデータ型とReaderをランタイムタイプとして定義する必要があるようで、これをオーバーライドしました。
4.頂点情報の取得
これで、エントリポイントからようやく情報が取れるようになりました。
Processメソッドで基底メソッドをコールして情報を読み込み、ModelContentクラスを引数としたメソッドを作成し、この内容から頂点情報、インデックス情報、テクスチャー情報を取得して、自前のデータフォーマットであるModelVertexDataクラスに情報を設定できるようにしました。
5.モデルに格納
自前のフォーマットにデータを保存できたまでは良いのですが、この情報をどこに格納すればいいのだろう?とModelクラスを見ていたら、Tagというメンバ(object型)に任意のデータを設定できるみたいだったので、これを設定しました。
これにより、ContentManager.LoadにてModelクラスが取得でき、この中のTagにコンテントパイプラインで読みこんだ自前フォーマットが取得できるようになりました。
◇ ◇ ◇
とまぁ、モデルの頂点情報を取得するまでにかなりの四苦八苦を行いましたが、とりあえず背景のモデルの頂点が取得できたので、背景としての存在を設計できるようになりました。
XNA上でのここまでの作業を行う事と、C++でDirect3Dを用いて自前でシンプルに情報を取得する…という比較は価値観の違いで行えませんが、この時点で一般のアマグラマーの人にXNAを流行らせるにはかなりの苦労が必要なのかなぁ~という所感がありました。
(背景ヒットの無いシンプルなゲーム制作か、自然とオープンなライブラリが充実してくれば話は別ですが…)
また、後で分ったのですが、この読み込みはキャッシュされる(or 読み込みは一度だけ)の模様で、ContentManager.Loadされた後に取得できるModelクラスのTagをnull化してしまうと、二度目以降のロードではTagに情報が読み込まれないみたいです。(本ブログの時点の「XNA Game Studio Express 1.0 Refresh」でのお話です)
◇ ◇ ◇
【おわりに】
文頭に「※これは2007/4月の頃のお話です。」という注釈をしましたが、この後にXNAのバージョンが上り、VertexBuffer.GetDataメソッドがWriteOnlyではなくなりました。
しかし、コンテントパイプラインの勉強という意味では良かったので、モデル情報の細かいハンドリングもできそうですし、コレはコレで使って行こうと思いました。
ただし、モデルの中にも頂点情報があるのに、更に自前のフォーマットでその複製があるのがかなり痛いです…
将来はこの辺りをどうするかを検討していかなければなりせん(何年後になるのか、それともこのプログラミングが途中で終了するのか…)
そして、本来は「ゲームプログラミング [XNA]」のカテゴリに載せるべき内容ではありますが、正直キチンと説明できるレベルではなかったので、開発日記としたレベルで、このカテゴリーに書くことにしました。
XNAで3Dのアクションゲームを作成するにあたり、背景のモデリングとその背景の座標情報をどのように設計するか検討しました。
基本的にキャラクターも背景も3Dのモデリングで作成する訳なので、ツールはMetasequoiaを使用しようと考えた訳なのですが、「背景の座標情報をどのようにして取得するのだろう?」と考えたところ、以下のアプローチが思い浮びました。
【モデルクラスの中を覗いて取得する】
まず普通に考えれば、XNAでモデルファイル(Xファイル)の情報が読み込め、これがModelクラスに格納され、これをforeachでメッシュ単位で描画まで行えているのだから、ここに座標情報が無いのはおかしいと思い、C#のコンパイラのメンバ表示で中をどんどん掘り下げていったところ、おおまかな階層でいうと、
Model.Meshes[配列].ModelMesh.VertexBuffer.GetData
というメソッドがあったので(ちなみにこれはジェネリクスで型定義がされていました)、「よし、これだっ!」という感じでDirect3Dで定義されているような型をXNA上で探し、これをジェネリクスタイプとして定義して呼び出したところ、エクセプションが走りました…(^^;
「あれれ? "GetData"なのになんでだろ? 型が不正なのかな?」と思い、エクセプションの内容を見ると「WriteOnly」…
「えぇ~? 型が不正というよりも、"GetData"なのに"書き込み専用"って、何???」みたいな状況となりました。
とりあえず、モデルの頂点情報が取得できないのであれば、背景のヒット判定ができないので、次の手法を検討しました。
【コンテントパイプラインを実装して取得する】
上述の方法がシンプルとは思いましたが、よく分りませんがメソッドがWriteOnlyですし、当初のXNAアーキテクチャーではこの方法がセオリーっぽく感じたので、これを実装することに…
とりあえず新しいプロジェクトとして、背景用のコンテントパイプラインを作成して、現在のソリューションに追加しました。
…と簡単に言いましたが、コンテントパイプラインの実装には本当に骨が折れました。
まずなにより「情報が殆んどない!」という流れから始まり、全世界をネットでかけずり周りながら、以下の順番で障壁にブツかりまくりました。
(『趣味』にしてはかなりキツイ作業としかいいようがない位…)
1.エントリポイントの作り方
とりあえずプロジェクト名を"BGContent"とした、クラスライブラリとしてプロジェクトを作成し、現在のソリューションに加えました。
そして、ModelProcessorクラスから派生させたBackgroundModelProcessorというクラスを実装し始めました。
このクラスにエントリポイント(正確にはコールバック)となるProcessというメソッドをオーバーライドして、そのメソッドの最初に基底クラスのメソッドを呼び出してみて、既存のモデルがコンテントパイプラインとして機能するか実験してみました。
/// <summary>背景のモデル情報をスキャン用コンテントパイプライン</summary> [ContentProcessor(DisplayName = "Backgroung Processor")] public class BackgroundModelProcessor : ModelProcessor { /// <summary>コンテントパイプライン処理を行います</summary> /// <param name="input">ノード情報</param> /// <param name="context">プロセスコンテキスト</param> /// <returns>読み込みが完了してモデル</returns> public override ModelContent Process( NodeContent input, ContentProcessorContext context) { // 既存の仕組みでモデルを読み込みます ModelContent model = base.Process(input, context); // ここに情報読みこみメソッドを実装 return model; } } |
とりあえずまずは成功したっぽかったので、これを登録した背景となるモデルのプロパティの"Content Processor"に登録してみて、この背景のモデルが読み込まれるか実験してみました。
ちゃんとコンテントプロセッサができなければXファイルのコンパイルの時点でエラーとなります。(なりました(^^;)

ここでポイントなのは戻り値が普段扱わないModelContentクラスというのも驚きました。「なんだこのクラス、見たことない!」と、まるでレアアイテムのようなノリで調べても、ネットにも情報がない…という始末でした。
2.頂点情報の取得 …その前に
ModelContentというクラスが取得できたので、この中を例の如くコンパイラのメンバ表示で覗いてみると、MeshesやらBonesやらとお宝情報が見えてきたので、なんとかしてコイツの情報を取得したいと情報を求めてネット上でアメリカ辺りをウロついていたら、この情報を取得する方法について説明があるのを発見しました。
これを読むと「ん? ContentTypeReader、ContentTypeWriterが必要…?」、よくよく考えると、コンテントパイプラインというのはモデル等のコンテンツ情報を読んで、Windowsや、Xbox 360のプラットフォームに合せて変換して出力する…という流れのためのものなので、リーダーやライターがあって当然…
ということで、これの実装を試みました。
とりあえず現状はWindowsのみで進める予定なので、エンディアンとか気にしないで、そのまま頂点、頂点インデックス、テクスチャーファイル名の情報を入出力することにしました。
この入出力の実装により、コンパイル時にコンテンツのコンパイル(変換)が行われ、実行ファイルと共に変換されたコンテンツが出力される寸法です。(多分…)
…が、しかし…
3.モデル情報を格納する型を決める
ここで情報のI/Oのために自前でフォーマットを用意するようで、頂点情報のバッファ、頂点インデックスバッファ、テクスチャーファイル名のバッファ(これは将来属性でもいいかな…と)を内包したクラスを作成し、これをReader, Writerのジェネリクスタイプにすることにしました。
また、Writerでは、このデータ型とReaderをランタイムタイプとして定義する必要があるようで、これをオーバーライドしました。
/// <summary>頂点情報保持クラス</summary> public class ModelVertexData { // テクスチャ及び頂点情報 public VertexPositionNormalTexture[] m_Vertices; // インデックスバッファ情報 public int[] m_IndexBuffer; // テクスチャファイル名 public string[] m_TextureNames; /// <summary>コンストラクタ</summary> /// <param name="Vertices">頂点情報</param> /// <param name="IndexBuffer">インデックスバッファ</param> /// <param name="textureNames">ファイル名集合(3頂点数と同じ)</param> public ModelVertexData( VertexPositionNormalTexture[] Vertices, int[] IndexBuffer, string[] textureNames) { m_Vertices = Vertices; m_IndexBuffer = IndexBuffer; m_TextureNames = textureNames; } /// <summary>頂点を取得します</summary> /// <param name="index">取得する頂点インデックス</param> /// <param name="Vertics">取得する 3 頂点</param> public void GetVector( int index, ref VertexPositionNormalTexture[] Vertics) { Vertics[0] = m_Vertices[m_IndexBuffer[index]]; Vertics[1] = m_Vertices[m_IndexBuffer[index + 1]]; Vertics[2] = m_Vertices[m_IndexBuffer[index + 2]]; } } /// <summary>書き込み用コンテントタイプライタ</summary> [ContentTypeWriter] public class ModelVertexDataWriter : ContentTypeWriter<ModelVertexData> { /// <summary>書き込みメソッド</summary> /// <param name="output">ライター</param> /// <param name="value">頂点情報</param> protected override void Write( ContentWriter output, ModelVertexData value) { // メンバを書き出します output.Write((int)value.m_Vertices.Length); for (int i = 0; i < value.m_Vertices.Length; i++) { output.Write(value.m_Vertices[i].Position); output.Write(value.m_Vertices[i].Normal); output.Write(value.m_Vertices[i].TextureCoordinate); } output.Write(value.m_IndexBuffer.Length); for (int i = 0; i < value.m_IndexBuffer.Length; i++) { output.Write(value.m_IndexBuffer[i]); } output.Write(value.m_TextureNames.Length); for (int i = 0; i < value.m_TextureNames.Length; i++) { output.Write(value.m_TextureNames[i]); } } /// <summary>ランタイムタイプを取得します</summary> /// <param name="targetPlatform">未使用</param> /// <returns>ランタイムタイプ名</returns> public override string GetRuntimeType( TargetPlatform targetPlatform) { return typeof(ModelVertexData).AssemblyQualifiedName; } /// <summary>リーダーランタイム情報の取得を行います</summary> /// <param name="targetPlatform">未使用</param> /// <returns>ランタイム情報</returns> public override string GetRuntimeReader( TargetPlatform targetPlatform) { return "BGContent.ModelVertexDataReader, BGContent, Version=1.0.0.0, Culture=neutral"; } } |
4.頂点情報の取得
これで、エントリポイントからようやく情報が取れるようになりました。
Processメソッドで基底メソッドをコールして情報を読み込み、ModelContentクラスを引数としたメソッドを作成し、この内容から頂点情報、インデックス情報、テクスチャー情報を取得して、自前のデータフォーマットであるModelVertexDataクラスに情報を設定できるようにしました。
5.モデルに格納
自前のフォーマットにデータを保存できたまでは良いのですが、この情報をどこに格納すればいいのだろう?とModelクラスを見ていたら、Tagというメンバ(object型)に任意のデータを設定できるみたいだったので、これを設定しました。
これにより、ContentManager.Load
◇ ◇ ◇
とまぁ、モデルの頂点情報を取得するまでにかなりの四苦八苦を行いましたが、とりあえず背景のモデルの頂点が取得できたので、背景としての存在を設計できるようになりました。
XNA上でのここまでの作業を行う事と、C++でDirect3Dを用いて自前でシンプルに情報を取得する…という比較は価値観の違いで行えませんが、この時点で一般のアマグラマーの人にXNAを流行らせるにはかなりの苦労が必要なのかなぁ~という所感がありました。
(背景ヒットの無いシンプルなゲーム制作か、自然とオープンなライブラリが充実してくれば話は別ですが…)
また、後で分ったのですが、この読み込みはキャッシュされる(or 読み込みは一度だけ)の模様で、ContentManager.Load
◇ ◇ ◇
【おわりに】
文頭に「※これは2007/4月の頃のお話です。」という注釈をしましたが、この後にXNAのバージョンが上り、VertexBuffer.GetDataメソッドがWriteOnlyではなくなりました。
しかし、コンテントパイプラインの勉強という意味では良かったので、モデル情報の細かいハンドリングもできそうですし、コレはコレで使って行こうと思いました。
ただし、モデルの中にも頂点情報があるのに、更に自前のフォーマットでその複製があるのがかなり痛いです…
将来はこの辺りをどうするかを検討していかなければなりせん(何年後になるのか、それともこのプログラミングが途中で終了するのか…)
そして、本来は「ゲームプログラミング [XNA]」のカテゴリに載せるべき内容ではありますが、正直キチンと説明できるレベルではなかったので、開発日記としたレベルで、このカテゴリーに書くことにしました。
| HOME |