XNA Framework 第 04 回
赤坂玲音 著
XNA Game Studio 2.0
これまで、Visual C# 2005 Express Edition + XNA Game Studio Express 1.0 という組み合わせで XNA Framework ゲームを開発してきました。この組み合わせは XNA
の基礎とクラスライブラリの仕組みを理解する上で重要ですが、すでに古くなりつつあります。今年の年末には XNA Game Studio 3.0 のリリースも予定されているため、今回からは XNA Game Studio 2.0 に対応した開発を行いましょう。
XNA Game Studio 2.0 は、XNA Game Studio Express 1.0 の次のバージョンとなる開発環境です。基本的には、これまでと同様に Visual C# 2005 Express Edition
と組み合わせて使うことができますが、上位の Visual Studio 2005 製品にも対応しています。そのため、Visual Studio 2005 Professional のようなプロフェッショナル向けの環境でゲームを開発することができます。
基本的なハードウェア要件は変わらないため、すでに XNA Game Studio Express 1.0 をインストールして開発している場合は、XNA Game Studio 2.0 をダウンロードしてインストールするだけです。
XNA Game Studio 2.0 が対象としている実行環境は、XNA Framework 2.0 です。これまでの XNA Framework 1.0 の機能に加えて、ネットワークなどの機能が追加されています。XNA Game Studio 2.0 で作ったゲームを配布する場合、実行する PC 上にも
XNA Framework 2.0 が必要になります。
XNA Game Studio 2.0 のインストール
XNA Game Studio 2.0 をインストールするには、あらかじめ Visual Studio 2005
をインストールしてください。学習目的や個人での利用の範囲であれば、無償で提供されている Visual C# 2005 Express Edition
で十分です。また、インストールした Visual Studio 2005 をアップデートして最新の状態にしてください。
その後、XNA Game Studio 2.0 をダウンロードしてセットアップを開始します。インストールの前に、Visual Studio
2005 や Visual C# 2005 Express Edition などは閉じてください。
■ 図 01 XNA Game Studio 2.0 セットアップの開始

図 01 のようなダイアログが表示されるので「Next」ボタンを押してセットアップを続けます。次に、ソフトウェアの使用許諾契約書が表示されます。
■ 図 02 使用許諾契約

内容に同意する場合は「I accept the terms in the License
Agreement.」にチェックを入れて「Next」ボタンを押します。
次に、Windows ファイアウォールに開発用のルールを追加するかどうかを指定します。開発環境をインストールする PC と Xbox 360
が通信するには、特定のポートを開放しなければなりません。Windows ファイアウォールを使っている場合で、Xbox 360
上で実行する場合や、ネットワーク機能を使う場合は「Yes, I wish to select these rules to
enable:」の項目すべてにチェックを入れてください。
■ 図 03 ファイアウォールの設定

以上で、セットアップは終了です。「Install」ボタンを押すと XNA Game Studio 2.0 のインストールが開始します。
■ 図 04

インストールが正しく終了すれば作業は完了です。Visual Studio 2005 または Visual C# 2005 Express
Edition を起動してください。
プロジェクトの作成
XNA Framework 2.0 を対象としたゲームを開発するには「新しいプロジェクト」ダイアログボックスの「プロジェクトの種類」ツリーから「XNA
Game Studuio 2.0」を選択し、右側の「テンプレート」リスト内の「Windows Game (2.0)」を選択してください。Xbox 360
用のゲーム開発には「Xbox 360 Game (2.0)」を選択します。
■ 図 05 新しいプロジェクト

基本的な開発方法は、これまでの XNA Game Studuio Express 1.0 と同じです。
XNA Game Studuio 2.0 では、プロジェクト内で扱う画像やフォントなどのコンテンツは「Content」内にまとめられるようになりました。
■ 図 06 Content フォルダ

エクスプローラでプロジェクトを配置しているフォルダを見ると Content
という名前のフォルダが作られ、この中にファイルが保存されていることが確認できます。しかし、Content
内に別の参照設定が含まれていることに注目してください。Content
は単純なフォルダではなく、リソースを管理するためのサブプロジェクトという位置づけになります。Content サブプロジェクトは、Content フォルダ内の Content.contentproj
という名前で作られています。
Content サブプロジェクトによって、Windows 用のプロジェクトと Xbox 360
用のプロジェクトで、画像やフォントを個別に管理する必要がなくなりました。複数のプラットフォーム用のプロジェクト間で、同じ Content
サブプロジェクトを共有することができます。
Xbox360 用プロジェクトの作成と実行
XNA Game Studio 2.0
では、より簡単に他のプラットフォーム用のゲームを開発することができるようになりました。これまでのように、個別に各プラットフォーム用のコードやリソースをコピーする必要はありません。ソリューション
エクスプローラから対象のプロジェクトを選択し、メニュー「プロジェクト」から「Create Copy of PROJECT for
TARGET」を選択してください。実際には、PROJECT にはプロジェクト名、TARGET には対象のプラットフォームが書かれています。
■ 図 06 プロジェクトの移植

例えば、Windows 用のゲームプロジェクト Windows Minesweeper を選択している場合「Create Copy of
WindowsMinesweeper for Xbox 360...」という項目が表示されます。この項目を選択すると、対象のプロジェクトから Xbox 360
用のプロジェクトを複製します。以下のダイアログが表示されるので「OK」ボタンを押してください。
■ 図 07 Xbox360 用プロジェクトを作成する確認ダイアログ

これで、元のプロジェクトを複製した Xbox 360
専用のプロジェクトが作成されます。ソースコードや画像などの項目は、すべて元のプロジェクトと同じファイルを参照しています。そのため、元のプロジェクトのファイルを編集すると、Xbox 360 プロジェクトにも反映されます。
これまで、XNA Game Studio Express 1.0 で作成したゲームを Xbox 360 で起動するには XNA Game Launcher を使っていました。
しかし、XNA Game Studio 2.0 で作成したゲームを起動するには XNA Game Launcher に代わって XNA Game Studio Connect
という名前のランタイムが必要になります。XNA Game Studio Connect は XNA Game Launcher と同じように Xbox Live
マーケットプレースから無料でダウンロードできます。
Xbox 360 でゲームを実行するには、実行する Xbox 360 本体を認識させなければなりません。XNA Game Studio
Connect を起動すると接続用のキーが表示されるので、XNA Game Studio
に接続キーを入力します。キーを入力するには、メニューの「ツール」から「Launch XNA Game Studio Device
Center」項目を選択してください。
■ 図 08 XNA Game Studio Device Center

このツールは XNA Game Studio
とは独立しているため、単独のアプリケーションとして実行されます。「スタート」の「すべてのプログラム」→「Microsoft XNA Game Studio
2.0」→「XNA Game Studio Device Center」を選択しても起動できます。
新しく Xbox 360 本体を登録するには 「Add Device」ボタンを押してください。図 09 のようなウィザードが表示されます。
■ 図 09 本体の名前

「Xbox 360 Name」には、新しく登録する Xbox 360 の名前を指定します。この名前は Xbox 360
本体を識別するための任意の名前です。わかりやすい適当な名前を入力して「Next」ボタンを押します。
次に、XNA Game Studio Connect 上で表示されている接続キーを入力します。
■ 図 10 接続キーの入力

テキストボックスに、接続する Xbox 360 で実行されている XNA Game Studio Connect が表示した接続キーを入力してください。1
と I など、紛らわしい文字に注意してください。正しく入力できれば「Next」ボタンを押してください。
次に、Xbox 360 への接続確認が行われます。正しく接続することができれば図 11
のような結果が表示されるので「Finish」を押して作業を完了します。失敗した場合は、ネットワークに問題があるか、接続キーが間違っている可能性があります。
■ 図 11 接続成功

以上で、Xbox 360 本体の登録作業は終了です。図 12 のように入力した Xbox 360
の名前が表示されていれば成功です。プロジェクトを実行するとき、選択中の本体にゲームが配置されます。
■ 図 12 登録された本体

Xbox 360 用のプロジェクトを配置して、ゲームを Xbox 360
上で実行できるかどうか、確認してください。実行方法に大きな違いはありません。XNA Game Studio Connect
が接続を待機している状態で、プロジェクトを実行すれば、自動的に対象の本体に配置されます。
コンテンツ管理
XNA Framework 2.0 は、基本的に XNA Framework 1.0 を拡張したものなので、これまで学習した XNA Framework
1.0 の知識をそのまま応用することができます。しかし、Game クラスのコンテンツの管理の部分で劇的に変化している部分があります。
Xbox 360 のように常に描画対象のデバイスが変化しない環境では問題ありませんでしたが、Windows
のようにゲーム画面の描画対象となるデバイスが変化する環境では、デバイスの消失によるコンテンツの管理が問題になりました。
マルチディスプレイ環境でゲーム画面を描画しているウィンドウを別のディスプレイに移動させると、これまで使っていた GraphicsDevice
オブジェクトが無効になり、新しい描画対象のデバイスに対応する GraphicsDevice オブジェクトが生成されました。これに合わせて、GraphicsDevice
に関連付けていたコンテンツを初期化する必要があります。
XNA Framework 2.0 では、このようなデバイスの消失がなくなったため、コンテンツの管理が簡素化されています。
ゲームの現在のデバイスは、Game クラスの GraphicsDevice から取得できるようになりました。そのため、Game クラスを継承するクラス内で
GraphicsDeviceManager を生成してデバイスを管理する必要はありません。プロパティから直接取得することができます。
■ Game クラス GraphicsDevice プロパティ
public GraphicsDevice GraphicsDevice { get; }
加えて、デバイスが消失しないのでコンテンツの管理が容易になっています。Game クラス以外の他のクラス内で GraphicsDevice
に関連するコンテンツを生成する場合でも、デバイスの生成や消失を意識する必要がなくなりました。
また、ContentManager のインスタンス化も不要になりました。Game クラスの Content プロパティから取得することができます。
■ Game クラス Content プロパティ
public ContentManager Content { get; }
このプロパティが返す ContentManager オブジェクトを使って、テクスチャやフォントなどのアセットを読み込むことができます。
XNA Framework 2.0 では、LoadGraphicsContent() に代わって LoadContent()
メソッドをオーバーライドして、このメソッド内でデータを読み込みます。
■ Game クラス LoadContent() メソッド
protected virtual void LoadContent ()
デバイスの消失がなくなったため、このメソッドはゲームの起動時に Initialize() メソッドから一度だけ呼び出されます。LoadGraphicsContent()
メソッドも互換性のために残されていますが、今後は LoadContent() メソッドの使用が推奨されます。
コンテンツを解放するには UnloadGraphicsContent() に代わって UnloadContent() メソッドをオーバーライドします。
■ Game クラス UnloadContent() メソッド
protected virtual void UnloadContent ()
LoadContent() メソッドと対になる形で、XNA Framework 2.0 では、このメソッドを使うことが推奨されています。デバイスが消失することはないため、メソッドが呼び出されるのはゲームが終了する直前です。
■ Sample01
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
public class Test : Game
{
public static void Main(string[] args)
{
using (Game game = new Test()) game.Run();
}
private GraphicsDeviceManager graphics;
private SpriteBatch sprite;
private Texture2D texture;
public Test()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void LoadContent()
{
sprite = new SpriteBatch(GraphicsDevice);
texture = Content.Load<Texture2D>("TestTexture");
base.LoadContent();
}
protected override void UnloadContent()
{
sprite.Dispose();
Content.Unload();
base.UnloadContent();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);
sprite.Begin();
sprite.Draw(texture, Vector2.Zero, Color.White);
sprite.End();
base.Draw(gameTime);
}
}
■ 実行結果

Sample01 は、LoadContent() メソッドをオーバーライドしてテクスチャを読み込み、Draw()
メソッドで描画するサンプルです。Game オブジェクトが既に GraphicsDevice や ContentManager
オブジェクトを持っているため、XNA Franework 1.0 の LoadGraphicsContent()
メソッドを使ったコードに比べて、簡素化できていることが確認できます。また、この変更に合わせて DrawableGameComponent クラスでも LoadContent() メソッドと UnloadContent() メソッドが追加されています。
ただし、デバイスが変更されるとデバイスの設定が破棄されてしまうので、Update() メソッドや Draw()
メソッド以外の場所で設定した、フレーム間で共有している設定は初期化しなければなりません。これまでの場合、GraphicsDevice
オブジェクトが再生成されると LoadGraphicsContent() メソッドが呼び出されていたため、このタイミングで GraphicsDevice
に初期値を設定することができましたが、LoadContent() メソッドは起動時に 1 度呼び出されるだけです。
頂点バッファ
これまで描画してきたプリミティブの頂点は、アプリケーションで用意した頂点の配列を GraphicsDevice
に渡して描画するというものでした。頂点のデータはメモリに保存されているため、描画する時に頂点のデータをメモリから GPU に転送しなければなりません。
よりパフォーマンスを向上させるために、頂点バッファと呼ばれる頂点を保存する専用のメモリ領域を使う方法が用意されています。頂点バッファは、システムによって管理され、より高速に描画できるように最適化されます。事前にデータを頂点バッファに転送し、頂点バッファを使ってプリミティブを描画します。多くの場合、頂点バッファを使ったほうがパフォーマンスが向上します。
頂点バッファを利用するには、頂点バッファを表す Microsoft.Xna.Framework.Graphics.VertexBuffer クラスを使います。
■ Microsoft.Xna.Framework.Graphics.VertexBuffer クラス
public class VertexBuffer : GraphicsResource
このクラスのコンストラクタで、頂点バッファを利用するデバイスやバッファのサイズなどを設定します。
■ VertexBuffer クラスのコンストラクタ
public VertexBuffer (
GraphicsDevice graphicsDevice,
int sizeInBytes,
BufferUsage usage
)
graphicsDevice パラメータに、この頂点バッファに関連付ける GraphicsDevice オブジェクトを指定します。sizeInBytes
パラメータにはバイト単位のバッファのサイズを指定します。XNA Framework で用意されている頂点には、必ず SizeInBytes
という名前の静的プロパティが用意されています。
■ SizeInBytes プロパティ
public static int SizeInBytes { get; }
使用する頂点の型が提供する SizeInBytes プロパティの値に、頂点の数を掛けることで必要なバッファのサイズを算出できます。
最後の usage パラメータには、バッファの使用方法を Microsoft.Xna.Framework.Graphics.BufferUsage 列挙型のメンバから指定します。
■ Microsoft.Xna.Framework.Graphics.BufferUsage 列挙型
[FlagsAttribute]
public enum BufferUsage
特にバッファの用途を指定しない場合は None メンバを指定します。ポイントスプライトと呼ばれる特殊な演出に使われる頂点の場合は Points
メンバを指定します。書き込み専用の頂点バッファの場合には WriteOnly メンバを指定します。
最適なパフォーマンスを得るには WriteOnly を選択します。WriteOnly
を指定したバッファは書き込み専用なので、データの設定は可能ですが、アプリケーションが設定したデータを読み込むことができなくなります。その代り、システムはバッファを最も効果的な場所にデータを保存することができます。
VertexBuffer クラスのインスタンスを生成することができれば、次に SetData()
メソッドで頂点のデータをバッファに設定しなければなりません。
■ VertexBuffer クラス SetData() メソッド
public void SetData<T> (
T[] data
) where T : ValueType
型パラメータ T には、設定する頂点の型を指定します。data
パラメータには、バッファに保存する頂点の配列を指定します。ここで設定する頂点は、コンストラクタで指定したバッファのバイトサイズを超えてはいけません。
バッファを用意することができれば、次に GraphicsDevice にバッファを設定します。バッファの設定には Vertices プロパティを使います。
■ GraphicsDevice クラス Vertices プロパティ
public VertexStreamCollection Vertices { get; }
このプロパティは、頂点バッファを設定する頂点ストリームと呼ばれるオブジェクトを管理するMicrosoft.Xna.Framework.Graphics.VertexStreamCollection クラスのオブジェクトを取得できます。
■ Microsoft.Xna.Framework.Graphics.VertexStreamCollection クラス
public sealed class VertexStreamCollection
このクラスのインデクサから、頂点ストリームを取得することができます。頂点ストリームはインデックスで管理され、頂点のデータを異なる頂点ストリームに分散して格納することも可能です。
■ VertexStreamCollection クラスのインデクサ
public VertexStream this [
int index
] { get; }
通常は、0 番の頂点ストリームを使うことになります。頂点ストリームは Microsoft.Xna.Framework.Graphics.VertexStream クラスで表されます。
■Microsoft.Xna.Framework.Graphics.VertexStream クラス
public sealed class VertexStream
頂点ストリームに頂点バッファを設定するには SetSource() メソッドを使います。
■ VertexStream クラス SetSource() メソッド
public void SetSource (
VertexBuffer vb,
int offsetInBytes,
int vertexStride
)
vb パラメータに、設定する頂点バッファを指定します。offsetInBytes パラメータには、vb
に指定した頂点バッファから使用する頂点の開始位置をバイト単位で指定します。すべての頂点を使用する場合は 0 を指定すればよいでしょう。最後の
vertexStride パラメータには、頂点バッファの 1 要素のサイズをバイト単位で指定します。通常は、頂点バッファに保存している頂点の構造体が提供している SizeInBytes
プロパティを指定します。
これで、頂点バッファをデバイスに設定する作業は終了です。設定している頂点バッファからプリミティブを描画するには DrawPrimitives()
メソッドを使います。
■ GraphicsDevice クラス DrawPrimitives() メソッド
public void DrawPrimitives (
PrimitiveType primitiveType,
int startVertex,
int primitiveCount
)
DrawUserPrimitives() メソッドと異なり、頂点のデータをパラメータで指定しない点に注目してください。primitiveType
パラメータに描画するプリミティブの種類、startVertex パラメータに頂点バッファの要素の中から開始する頂点、primitiveCount パラメータにプリミティブの数を指定します。このメソッドでは頂点のデータを渡していませんが、設定している頂点バッファが代わりに用いられます。
■ Sample02
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 graphics;
private VertexBuffer buffer;
private VertexDeclaration dec;
private BasicEffect effect;
public Test()
{
graphics = new GraphicsDeviceManager(this);
}
protected override void Initialize()
{
effect = new BasicEffect(GraphicsDevice, null);
effect.VertexColorEnabled = true;
VertexPositionColor[] data = new VertexPositionColor[] {
new VertexPositionColor(new Vector3(-0.8F, -0.8F, 0), Color.Red),
new VertexPositionColor(new Vector3(0, 0.8F, 0), Color.Blue),
new VertexPositionColor(new Vector3(0.8F, -0.8F, 0), Color.Green)
};
buffer = new VertexBuffer(
GraphicsDevice, VertexPositionColor.SizeInBytes * data.Length, BufferUsage.None);
buffer.SetData(data);
dec = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
base.Initialize();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.VertexDeclaration = dec;
GraphicsDevice.Vertices[0].SetSource(buffer, 0, VertexPositionColor.SizeInBytes);
GraphicsDevice.Clear(Color.White);
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
pass.End();
}
effect.End();
base.Draw(gameTime);
}
}
■ 実行結果

これまでは、頂点の配列をメソッドに渡してプリミティブを描画していましたが、Sample02 では DrawPrimitives()
メソッドを使って事前にデバイスに設定されている頂点を使ってプリミティブを描画しています。
このプログラムでは Initialize() メソッド内で VertexBuffer
オブジェクトを生成し、事前に用意した頂点の配列をバッファに設定しています。結果の見た目に大きな違いはありませんが、頂点データの転送がボトルネックとなっている場合、この方法を用いることでパフォーマンスが改善する可能性があります。
インデックスバッファ
長方形の物体を描画する場合、2 つの三角形を繋ぎ合わせて 1
つの長方形としました。どのような複雑な物体も、基本は組み合わされた三角形です。ところが、長方形を構成する 2 つの三角形の頂点には、2
点だけ共通する座標の頂点があります。
■ 図 13 重複する頂点

図 13 は、長方形を構成する 2 つの三角形の頂点を表したものです。この場では、簡単にするために Z 座標は考えない 2
次元の長方形とします。長方形の左上隅と、右下隅の座標が重複していることがわかります。
この共通する 2
つの頂点を個別に用意するのは非効率的です。複数のプリミティブで同じ頂点が使われる場合、頂点を共有することができればメモリを効率的に使うことができます。これを実現するのが、頂点を参照するインデックスバッファと呼ばれるデータです。
インデックスバッファは、頂点の配列の中から、描画するプリミティブが使用する要素のインデックスを保存しているバッファです。インデックスバッファを用いて長方形を描画する場合、長方形の角となる
4 つの頂点を用意し、これとは別に、使用する頂点データの要素番号を保持する整数型の配列を用意します。
■図14 インデックスによる頂点の参照

プリミティブの描画には、頂点バッファの要素を指すインデックスバッファの値を使うことができます。インデックスバッファからプリミティブを描画するには、通常の変数としてメモリ上に配置されている頂点とインデックスを使う方法と、頂点バッファの場合と同じようにデバイスに設定する
2 種類の方法があります。
変数から直接描画するには DrawUserIndexedPrimitives() メソッドを使います。
■ GraphicsDevice クラス DrawUserIndexedPrimitives() メソッド
public void DrawUserIndexedPrimitives<T> (
PrimitiveType primitiveType,
T[] vertexData,
int vertexOffset,
int numVertices,
short[] indexData,
int indexOffset,
int primitiveCount
) where T : ValueType
public void DrawUserIndexedPrimitives<T> (
PrimitiveType primitiveType,
T[] vertexData,
int vertexOffset,
int numVertices,
int[] indexData,
int indexOffset,
int primitiveCount
) where T : ValueType
このメソッドのパラメータは、頂点に加えてインデックスの配列を要求しています。primitiveType パラメータには、プリミティブの種類を指定します。vertexData
パラメータには、頂点の配列を指定します。vertexOffset パラメータには、開始する頂点を指定します。numVertices パラメータには、頂点の数を指定します。DrawUserPrimitives()
メソッドと異なり、頂点の数とプリミティブの数は関係しません。
プリミティブの描画には、indexData パラメータに指定した頂点の要素を指す整数の配列が使われます。インデックスには、16 ビットの short
型と、32 ビットの int 型の両方が使えますが、通常はメモリの消費を抑えるために short 型を使います。
indexOffset パラメータには、indexData に指定した配列の中から使用する最初の要素のインデックスを指定します。primitiveCount
パラメータには、描画するプリミティブの数を指定します。インデックスを用いる場合、indexData に指定した整数の配列がプリミティブの数に関係します。2
つの三角形を描画する場合、少なくとも 6 つ以上の要素を持つ配列を指定しなければなりません。
よって、長方形を描画する場合は、長方形の角となる 4 つの頂点を用意し、これら 4
つのいずれかの頂点を指す整数型の配列を用意することで、頂点を共有して効率的にプリミティブを描画することができます。
■ Sample03
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 graphics;
private BasicEffect effect;
private VertexPositionColor[] vertices;
private VertexDeclaration dec;
private short[] indices;
public Test()
{
graphics = new GraphicsDeviceManager(this);
}
protected override void Initialize()
{
effect = new BasicEffect(GraphicsDevice, null);
effect.VertexColorEnabled = true;
vertices = new VertexPositionColor[] {
new VertexPositionColor(new Vector3(-0.8F, 0.8F, 0), Color.Red),
new VertexPositionColor(new Vector3(0.8F, 0.8F, 0), Color.Blue),
new VertexPositionColor(new Vector3(-0.8F, -0.8F, 0), Color.Green),
new VertexPositionColor(new Vector3(0.8F, -0.8F, 0), Color.Black),
};
indices = new short[] {
0, 1, 2,
1, 3, 2
};
dec = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
base.Initialize();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.VertexDeclaration = dec;
GraphicsDevice.Clear(Color.White);
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
GraphicsDevice.DrawUserIndexedPrimitives(
PrimitiveType.TriangleList, vertices, 0, 4, indices, 0, 2);
pass.End();
}
effect.End();
base.Draw(gameTime);
}
}
■ 実行結果

Sample03 は、長方形の各頂点を vertices 配列に保存し、頂点を参照するインデックスを short 型の indices
配列に格納しています。インデックスを用いるため、複数のプリミティブが頂点を共有することができます。長方形を作るために 2
つの三角形を描画しますが、用意した頂点は 4 つです。描画する三角形の各頂点は、頂点を参照するインデックスが用いられてます。
頂点バッファと同じように、インデックスバッファも事前にデバイスに設定して描画することができます。インデックスバッファをデバイスに設定するには
GraphicsDevice クラスの Indices プロパティを使います。
■ GraphicsDevice クラス Indices プロパティ
public IndexBuffer Indices { get; set; }
このプロパティに設定するインデックスバッファは、IndexBuffer
クラスのオブジェクトです。頂点バッファと同じように、このオブジェクトに事前にインデックスを保存します。
■ Microsoft.Xna.Framework.Graphics.IndexBuffer クラス
public class IndexBuffer : GraphicsResource
このクラスのコンストラクタは、次のようになります。
■ IndexBuffer クラスのコンストラクタ
public IndexBuffer (
GraphicsDevice graphicsDevice,
int sizeInBytes,
BufferUsage usage,
IndexElementSize elementSize
)
graphicsDevice パラメータには、このオブジェクトに関連付ける GraphicsDevice オブジェクトを指定します。sizeInBytes
パラメータには、このバッファのサイズをバイト単位で指定します。usage パラメータには、バッファの使用方法を指定します。ここまでは、基本的に VertexBuffer
クラスと同じです。
最後の elementSize パラメータは、インデックスの要素のサイズを表す Microsoft.Xna.Framework.Graphics.IndexElementSize 列挙型のいずれかのメンバを指定します。
■ Microsoft.Xna.Framework.Graphics.IndexElementSize 列挙型
public enum IndexElementSize
インデックスの要素に 16 ビットの整数を使う場合は SixteenBits メンバを、32 ビットの整数を使う場合は ThirtyTwoBits
メンバを指定します。
生成した IndexBuffer オブジェクトにインデックスを設定するには SetData() メソッドを使います。
■ IndexBuffer クラス SetData() メソッド
public void SetData<T> (
T[] data
) where T : ValueType
型パラメータ T は、インデックスの要素の型を指定します。コンストラクタで elementSize パラメータに SixteenBits
を指定している場合は short 型の配列、ThirtyTwoBits を指定している場合は int 型の配列を指定します。
これで、設定したインデックスバッファは、同じくデバイスに設定されている頂点バッファの要素を指すインデックスとして用いられます。インデックスバッファを用いてプリミティブを描画するには DrawIndexedPrimitives()
メソッドを使います。
■ GraphicsDevice クラス DrawIndexedPrimitives() メソッド
public void DrawIndexedPrimitives (
PrimitiveType primitiveType,
int baseVertex,
int minVertexIndex,
int numVertices,
int startIndex,
int primitiveCount
)
primitiveType パラメータには、プリミティブの種類を指定します。
baseVertex パラメータには、使用する頂点バッファの最初の要素のインデックスを指定します。minVertexIndex
パラメータには、使用する頂点の最小インデックスを指定します。numVertices は、使用する頂点の数を指定します。使われる頂点バッファの最初の要素は baseVertex
+ minVertexIndex から数えられます。頂点バッファ全体を使う場合、baseVertex と minVertexIndex には 0 を指定し、numVertices には頂点バッファの要素数を指定すればよいでしょう。
startIndex パラメータには、プリミティブの描画に使用するインデックスバッファ内の要素の開始インデックスを指定します。最後の primitiveCount パラメータにプリミティブの数を指定します。
■ Sample04
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 graphics;
private BasicEffect effect;
private VertexDeclaration dec;
private VertexBuffer vertexBuffer;
private IndexBuffer indexBuffer;
public Test()
{
graphics = new GraphicsDeviceManager(this);
}
protected override void Initialize()
{
effect = new BasicEffect(GraphicsDevice, null);
effect.VertexColorEnabled = true;
VertexPositionColor[] vertices = new VertexPositionColor[] {
new VertexPositionColor(new Vector3(-0.8F, 0.8F, 0), Color.Red),
new VertexPositionColor(new Vector3(0.8F, 0.8F, 0), Color.Blue),
new VertexPositionColor(new Vector3(-0.8F, -0.8F, 0), Color.Green),
new VertexPositionColor(new Vector3(0.8F, -0.8F, 0), Color.Black),
};
short[] indices = new short[] {
0, 1, 2,
1, 3, 2
};
vertexBuffer = new VertexBuffer(
GraphicsDevice, VertexPositionColor.SizeInBytes * vertices.Length, BufferUsage.None);
vertexBuffer.SetData(vertices);
indexBuffer = new IndexBuffer(
GraphicsDevice, sizeof(short) * indices.Length,
BufferUsage.None, IndexElementSize.SixteenBits);
indexBuffer.SetData(indices);
dec = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
base.Initialize();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionColor.SizeInBytes);
GraphicsDevice.Indices = indexBuffer;
GraphicsDevice.VertexDeclaration = dec;
GraphicsDevice.Clear(Color.White);
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2);
pass.End();
}
effect.End();
base.Draw(gameTime);
}
}
■ 実行結果

Sample04 で用意した頂点とインデックスの内容は Sample03 と同じです。用意した頂点の配列を VertexBuffer
に保存し、インデックスも同じように IndexBuffer に保存しています。そして、これらをデバイスに登録し
DrawIndexedPrimitives() で描画しています。
ビューポート
画面を分割して、複数のプレイヤーによる同時プレイを可能にしたり、画面内に別視点のミニウィンドウを表示したりするゲームを作るには、画面を個別に描画する必要があります。このような場合、デバイスの描画領域を指定するビューポートと呼ばれる範囲を設定します。
デフォルトではゲーム画面全体に描画されますが、ビューポートを変更することで特定の矩形領域に絞り込むことができます。画面分割を行うには、ビューポートを複数に分けて描画します。
ビューポートは GraphicsDevice クラスの Viewport プロパティで設定します。
■ GraphicsDevice クラス Viewport プロパティ
public Viewport Viewport { get; set; }
Viewport プロパティには、ビューポートの領域を表す Microsoft.Xna.Framework.Graphics.Viewport 構造体の値が格納されています。
■ Microsoft.Xna.Framework.Graphics.Viewport 構造体
[SerializableAttribute]
public struct Viewport
ビューポートの領域は、X プロパティと Y プロパティで描画対象となる矩形領域の左上隅の座標を表します。デフォルトでは、これらのプロパティは 0
に設定されています。
■ Viewport 構造体 X プロパティ
public int X { get; set; }
■ Viewport 構造体 Y プロパティ
public int Y { get; set; }
長方形の幅は Width プロパティ、高さは Height プロパティに設定します。
■ Viewport 構造体 Width プロパティ
public int Width { get; set; }
■ Viewport 構造体 Height プロパティ
public int Height { get; set; }
これらのプロパティの値は、すべてピクセル単位です。ビューポートを分割することで、画面内の特定の領域に異なる映像を描画できます。
■ Sample05
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 graphics;
private BasicEffect effect;
private VertexPositionColor[] vertices;
private VertexDeclaration dec;
public Test()
{
graphics = new GraphicsDeviceManager(this);
}
protected override void Initialize()
{
effect = new BasicEffect(GraphicsDevice, null);
effect.VertexColorEnabled = true;
vertices = new VertexPositionColor[] {
new VertexPositionColor(new Vector3(0F, 0.5F, 0), Color.Red),
new VertexPositionColor(new Vector3(0.5F, -0.5F, 0), Color.Blue),
new VertexPositionColor(new Vector3(-0.5F, -0.5F, 0), Color.Green),
new VertexPositionColor(new Vector3(-0.5F, 0.5F, 0), Color.Red),
new VertexPositionColor(new Vector3(0.5F, 0.5F, 0), Color.Blue),
new VertexPositionColor(new Vector3(-0F, -0.5F, 0), Color.Green)
};
dec = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
base.Initialize();
}
protected override void Draw(GameTime gameTime)
{
Viewport viewport = new Viewport();
viewport.Width = Window.ClientBounds.Width;
viewport.Height = Window.ClientBounds.Height / 2;
GraphicsDevice.VertexDeclaration = dec;
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
viewport.Y = 0;
GraphicsDevice.Viewport = viewport;
GraphicsDevice.Clear(Color.White);
GraphicsDevice.DrawUserPrimitives(
PrimitiveType.TriangleList, vertices, 0, 1);
viewport.Y = Window.ClientBounds.Height / 2;
GraphicsDevice.Viewport = viewport;
GraphicsDevice.Clear(Color.Black);
GraphicsDevice.DrawUserPrimitives(
PrimitiveType.TriangleList, vertices, 3, 1);
pass.End();
}
effect.End();
base.Draw(gameTime);
}
}
■ 実行結果

Sample05 は、配列型の変数 vertices に 2 つの三角形を描画するための 6 つの頂点を保存しています。Draw()
メソッドでこれを描画するとき、ビューポートをウィンドウの上下半分に分割しています。Clear()
メソッドで背景を白と黒に分けているため、ビューポートの範囲が確認できます。
このように、描画するプリミティブの移動や伸縮を行わなくても、ビューポートを変更することで特定の領域だけに絞り込んで描画できます。
透明度を反映させる
Color 構造体には、透明度を表すアルファ値も要素の 1
つとして含まれていました。頂点の色に半透明なアルファ値を設定した場合、プリミティブが透過することを期待するかもしれません。しかし、デフォルトの設定では半透明な色の合成は行われません。
通常、アルファ値は 1 を完全な不透明、0
を完全な透明とする透明度の制御に用いられます。この値を反映させ、プリミティブを半透明に描画するには、アルファ値を使った合成を許可し、背景との合成方法を設定しなければなりません。
デバイスの描画に関連した設定の多くは、GraphicsDevice クラスの RenderState プロパティから行います。
■ GraphicsDevice クラス RenderState プロパティ
public RenderState RenderState { get; }
このプロパティは、デバイスの描画に関連した設定を保持する Microsoft.Xna.Framework.Graphics.RenderState
クラスのオブジェクトを提供します。
■ Microsoft.Xna.Framework.Graphics.RenderState クラス
public sealed class RenderState
アルファ値による色の合成を許可するには、GraphicsDevice オブジェクトの RenderState プロパティが返した RenderState
オブジェクトの AlphaBlendEnable プロパティの値を true に設定します。
■ RenderState クラス AlphaBlendEnable プロパティ
public bool AlphaBlendEnable { get; set; }
このプロパティが true の場合、アルファ合成が有効になります。デフォルトでは false に設定されています。
次に、アルファ値からどのように色を合成するかを設定しなければなりません。色の合成には、描画するプリミティブのピクセルと、現在の画面のピクセルの 2
つの色が使われます。描画するピクセルの色は SourceBlend プロパティで、描画先のピクセルの色は DestinationBlend
プロパティで合成方法を指定します。
■ RenderState クラス SourceBlend プロパティ
public Blend SourceBlend { get; set; }
■ RenderState クラス DestinationBlend プロパティ
public Blend DestinationBlend { get; set; }
これらのプロパティには、合成方法を指定する Microsoft.Xna.Framework.Graphics.Blend 列挙型のいずれかのメンバを設定します。
■ Microsoft.Xna.Framework.Graphics.Blend 列挙型
public enum Blend
この列挙型には、色の合成方法を表すメンバが定義されています。最も簡単な合成は、すべての色の要素に 0 を掛ける Zero メンバと、すべての色の要素に 1
を掛ける One メンバです。Zero を指定した場合、元の色は完全に消失して、完全な透明の黒色を表す、すべての要素が 0 の色が作られます。One
を指定した場合は、元の色がそのまま使われます。例えば、SourceBlend に One、DestinationBlend に Zero
を指定した場合、描画するピクセルの色で元のピクセルの色を上書きします。
アルファ合成を行う場合、一般的に期待される結果を得るには SourceBlend プロパティに SourceAlpha
を指定し、DestinationBlend プロパティに InverseSourceAlpha を指定します。
■ Sample06
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 graphics;
private BasicEffect effect;
private VertexPositionColor[] vertices;
private VertexDeclaration dec;
public Test()
{
graphics = new GraphicsDeviceManager(this);
}
protected override void Initialize()
{
effect = new BasicEffect(GraphicsDevice, null);
effect.VertexColorEnabled = true;
Color color1 = new Color(0xFF, 0, 0, 0x80);
Color color2 = new Color(0, 0, 0xFF, 0x80);
Color color3 = new Color(0, 0xFF, 0, 0x80);
vertices = new VertexPositionColor[] {
new VertexPositionColor(new Vector3(-0.25F, 0.5F, 0), color1),
new VertexPositionColor(new Vector3(0.25F, -0.5F, 0), color1),
new VertexPositionColor(new Vector3(-0.75F, -0.5F, 0), color1),
new VertexPositionColor(new Vector3(0.25F, 0.5F, 0), color2),
new VertexPositionColor(new Vector3(0.75F, -0.5F, 0), color2),
new VertexPositionColor(new Vector3(-0.25F, -0.5F, 0), color2),
new VertexPositionColor(new Vector3(-0.5F, 0.5F, 0), color3),
new VertexPositionColor(new Vector3(0.5F, 0.5F, 0), color3),
new VertexPositionColor(new Vector3(0, -0.5F, 0), color3),
};
dec = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
base.Initialize();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.VertexDeclaration = dec;
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
GraphicsDevice.Clear(Color.White);
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
GraphicsDevice.DrawUserPrimitives(
PrimitiveType.TriangleList, vertices, 0, 3);
pass.End();
}
effect.End();
base.Draw(gameTime);
}
}
■ 実行結果

Sample06 は、重なり合う三角形の頂点の色を半透明に設定して描画したものです。それぞれの頂点のアルファ値が 0x80
に設定されています。三角形の背景が透過され、元のピクセルと合成されていることが結果から確認できます。
マインスイーパーを最適化する
今回は、前回作成したマインスイーパーを頂点バッファとインデックスバッファを使って最適化すると同時に、XNA Framework 2.0
に対応させましょう。Windows 用のプロジェクトと Xbox 360 用のプロジェクトで、同じ Content
サブプロジェクトを共有することで、コンテンツを個別に管理する必要がなくなりました。
前回までは、6 つの頂点による 2 つの三角形を組み合わせた 1
つの長方形を使っていました。立方体を描画するときには、ワールド変換で長方形の座標を移動・回転させて各面を描画していました。本稿では、これを頂点バッファとインデックスバッファを用いた描画方法に書き換えます。
まず、立方体の各頂点を配列に保存します。今回は、立方体の面となる長方形を 6 つ用意します。長方形は 2
つの三角形で構成されるため、インデックスを使って頂点を共有させましょう。頂点とインデックスの配列は、コンストラクタなどで初期化してください。
■ Minesweeper クラスのコンストラクタ 抜粋
//立方体の頂点
vertices = new VertexPositionTexture[] {
//背面
new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
//左側面
new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(-1, 1, 1), new Vector2(1, 0)),
new VertexPositionTexture(new Vector3(-1, -1, 1), new Vector2(1, 1)),
new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
//前面
new VertexPositionTexture(new Vector3(-1, 1, 1), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(1, 1, 1), new Vector2(1, 0)),
new VertexPositionTexture(new Vector3(1, -1, 1), new Vector2(1, 1)),
new VertexPositionTexture(new Vector3(-1, -1, 1), new Vector2(0, 1)),
//右側面
new VertexPositionTexture(new Vector3(1, 1, 1), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
new VertexPositionTexture(new Vector3(1, -1, 1), new Vector2(0, 1)),
//上面
new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
new VertexPositionTexture(new Vector3(1, 1, 1), new Vector2(1, 1)),
new VertexPositionTexture(new Vector3(-1, 1, 1), new Vector2(0, 1)),
//下面
new VertexPositionTexture(new Vector3(-1, -1, 1), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(1, -1, 1), new Vector2(1, 0)),
new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
};
//インデックス
indices = new short[] {
0, 1, 2, 0, 2, 3, //背面
4, 5, 6, 4, 6, 7, //左側面
8, 9, 10, 8, 10, 11, //前面
12, 13, 14, 12, 14, 15, //右側面
16, 17, 18, 16, 18, 19, //上面
20, 21 ,22, 20, 22, 23 //下面
};
次に、GraphicsDevice オブジェクトが確実に生成されている Initialize()
メソッド内で、頂点バッファとインデックスバッファを生成します。VertexBuffer オブジェクトを作成して、ここに頂点の配列を設定します。同様に IndexBuffer
オブジェクトを作成して、インデックスの配列を設定します。
■ Minesweeper クラス Initialize() メソッド
protected override void Initialize()
{
//地雷原のデータ
MineField defaultField = new MineField(10, 8);
defaultField.Random(10);
this.MineField = defaultField;
effect = new BasicEffect(graphics.GraphicsDevice, null);
effect.TextureEnabled = true;
vertexBuffer = new VertexBuffer(
GraphicsDevice, VertexPositionTexture.SizeInBytes * vertices.Length, BufferUsage.None);
vertexBuffer.SetData(vertices);
indexBuffer = new IndexBuffer(
GraphicsDevice, sizeof(short) * indices.Length,
BufferUsage.None, IndexElementSize.SixteenBits);
indexBuffer.SetData(indices);
dec = new VertexDeclaration(graphics.GraphicsDevice, VertexPositionTexture.VertexElements);
base.Initialize();
}
これで、頂点バッファとインデックスバッファの生成は完了です。Draw() メソッド内で、デバイスに頂点バッファとインデックスバッファを登録し、DrawIndexedPrimitives()
メソッドを使ってプリミティブを描画できます。
前回は、1
つの長方形を表す頂点の配列を使って、ワールド変換を繰り返しながら個別にプリミティブを描画していました。今回は、立方体のすべての面の頂点を事前に用意しているため、地雷原となる背面と、背面を隠すためのボックスの
2 回、描画するだけで済みます。
■ Minesweeper クラス Draw() メソッド
protected override void Draw(GameTime gameTime)
{
GraphicsDevice g = this.GraphicsDevice;
g.VertexDeclaration = dec;
g.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionTexture.SizeInBytes);
g.Indices = indexBuffer;
g.Clear(Color.White);
effect.Begin();
effect.CurrentTechnique.Passes[0].Begin();
for (int r = 0; r < mineField.Row; r++)
{
for (int c = 0; c < mineField.Column; c++)
{
int mines = mineField.GetAdjacentMines(c, r);
ImageName imageName = (ImageName)Enum.Parse(typeof(ImageName), mines.ToString(), true);
effect.World = Matrix.CreateTranslation(c * 2, r * -2, 0);
effect.View = view;
effect.Projection = projection;
if ((mineField[c, r] & FieldState.Mine) == FieldState.Mine) //地雷のマス
effect.Texture = images[ImageName.MineField];
else effect.Texture = images[imageName]; //地雷ではない
effect.CommitChanges();
//背面の描画
g.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, 2);
if ((mineField[c, r] & FieldState.Opened) != FieldState.Opened) //開かれていない
{
if (c == mineField.SelectedColumn && r == mineField.SelectedRow) //選択されている
effect.Texture = images[ImageName.Selector];
else effect.Texture = images[ImageName.HideField]; //選択されていない
effect.CommitChanges();
//ボックスの描画
g.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 6, 10);
}
}
}
effect.CurrentTechnique.Passes[0].End();
effect.End();
base.Draw(gameTime);
}
■実行結果

頂点バッファとインデックスバッファを設定してプリミティブを描画している点を除いて、前回のマインスイーパーの描画方法と大きな違いはありません。Update()
メソッド内でコントローラからの入力を処理し、カメラの座標やゲームデータを更新します。
本稿では、XNA Framework
の基礎を学習していただくために、基本的な機能のみを使ってゲームを描画してきました。独自のエフェクトやモデリングツールなどを使うことで、よりリッチなゲームを開発することができますが、特別な
3D
モデリング技術がなくても、アイデア次第で面白いゲームを作ることができるかもしれません。立方体を組み合わせたブロック関連のゲームであれば、これまでの内容を応用することで作ることができそうです。ぜひ、挑戦してみてください。
|