第 6 章 実践ゲーム開発~ DirectX によるゲームプログラミング入門 ~6.1 はじめに近年、ゲーム開発の技術は目覚しい発展を遂げ、ゲーム開発業界に大きな影響を与えてきました。また、最近では、パソコンの性能の向上や Microsoft 社の Xbox などの高性能なゲーム機器の登場により、コンピュータゲームは、年々高品質化の一途をたどっています。そのような背景の中、ゲームプログラミング初心者でも簡単に、高品質なゲームを開発できる開発環境として、DirectX が注目されています。 第 5 章では、本章で開発する 3D シューティングゲームの開発に必要なクラスの構成、3D シューティングゲームが実装する機能やゲームの内容などについて説明しました。そこで、本章では、第 5 章と第 6 章を通して作成する、3D シューティングゲームの実装方法について説明します。具体的には、第 5 章で説明した内容に従って、DirectX と Visual C++ .NET を利用して開発を行います。本章で説明する内容を図 1 に示します。 図 1 3D シューティングゲームの開発工程 6.2 ゲームプログラム固有の処理6.2.1 衝突判定衝突判定とは、空間上で 2 つの物体が重なっているかどうかを判断する処理です。衝突判定は、シューティングゲームやアクションゲームなどゲームのジャンルを問わず必要な処理です。例えば、3D シューティングゲームでは、弾と機体との接触を判定するために衝突判定を使用します。 (1) ゲームプログラミングの衝突判定一般的に、ゲームのキャラクターの多くは、非常に複雑な形状をしています。実際のゲームプログラミングにおいて、ゲームのキャラクターの複雑な形状に基づいて衝突判定を忠実に行うことは、膨大な計算量になるため、非現実的です。そのため、ゲームプログラミングでは、キャラクターの形状に近い単純な形状に基づいて衝突判定を行うことにより、計算量を減少します。例えば、ゲームプログラミングにおいて、衝突判定に使用する形状としては、2D を基にしたゲームを開発する場合は円や長方形、3D を基にしたゲームを開発する場合は球や直方体を利用します。本章で作成するシューティングゲームでは、2D のシューティングゲームを基礎として開発するため、円を使用して衝突判定を行います。円による衝突判定のイメージを図 2 に示します。 図 2 衝突判定のイメージ (2) 衝突判定のコード例衝突判定のコード例では、衝突判定を行うクラスの中でも重要な位置を占める CShots クラスの衝突判定について説明します。CShots クラスの衝突判定では、画面上に存在する全ての弾について衝突判定を行います。CShots クラスの衝突判定のコード例を次に示します。 |
bool CShots::HitTest(float fX, float fZ, float fR, char cShotType, bool bEraseShot)
{
int i; // ループカウンタ
for (i = 0;i < MAX_SHOTS;i++) // 弾の最大数だけループ
{
if (m_nodes[i].m_bActive == true) // 弾が存在する場合
{
// 判定対象の弾の種類でない場合は処理をスキップ
if (m_nodes[i].type != cShotType)
continue;
// 円の衝突判定を行い、衝突している場合
if (sqrt((double)((fX - m_nodes[i].m_vPosition.X)
* (fX - m_nodes[i].m_vPosition.X))
+ (double)((fZ - m_nodes[i].m_vPosition.Z)
* (fZ - m_nodes[i].m_vPosition.Z))) < fR)
{
if (bEraseShot) // 衝突判定が真なら弾を消すよう指示されている場合
m_nodes[i].m_bActive = false; // 弾を消す
return true; // trueを返して終了
}
}
}
return false; // falseを返して終了
}
ゲームプログラミングでは、ゲームにキャラクターなどを登場させる場合、モデルのデータ形式を決定する必要があります。モデルのデータ形式には、様々な種類があります。例えば、モデルのデータ形式には、一般的に良く知られている X ファイル形式があります。X ファイル形式は、DirectX に X ファイル専用の読み込み機能があるため、最も取り扱いが簡単です。しかし、X ファイル形式は、汎用性を重視したデータ構造になっているため、描画処理が極端に遅くなってしまいます。また、X ファイル形式は、高度なアニメーションの情報や衝突判定などのゲーム独自の情報をモデルに保持させることができないという欠点があります。
一般的に、実際のゲーム開発では、X ファイル形式ではなく独自のデータ形式を使用します。そこで、本章では、実際のゲーム開発を想定し、独自のデータ形式を決定します。また、独自のデータ形式に対応したモデルの読み込みルーチンや描画ルーチンを作成する方法についても解説します。
本章で作成する 3D シューティングゲームのモデルのデータ形式は、簡潔さを重視し、最低限の情報のみを保持します。本章で使用するモデルのデータ形式を表 1 に示します。
表 1 モデルのデータ形式
項目 | 内容 |
材質の数 | モデルデータに含まれる材質の種類の数を格納します。頂点の情報および面の情報は、材質ごとにグループ化して管理します。 |
頂点の数 | モデルデータに含まれる頂点の数を格納します。 |
面の数 | モデルデータに含まれる面の数を格納します。 |
材質の情報 | 材質の色の情報を格納します。 |
頂点の情報 | 頂点の位置と UV 座標を格納します。 |
面の情報 | 面を構成する頂点のインデックス情報を格納します。 |
表 1 に示したような単純なデータ形式では、X ファイルと比較した場合、大きな長所はありません。しかし、プログラムを拡張して大規模なゲーム開発を行う場合、独自のデータ形式は、処理速度や柔軟性の面で X ファイル形式と比較すると有利です。
3D シューティングゲームにおいて、プレイヤーの機体や敵キャラクターを表示するためには、モデルを読み込む必要があります。モデルを読み込むためには、モデルのデータ形式に対応した処理を実行する必要があります。表 1 で定義したモデルのデータ形式を読み込む処理について説明します。
モデルデータ全体に関する情報を読み込みます。モデルデータ全体に関する情報には、材質の数、頂点の数、面の数があります。モデルデータ全体の読み込みを行うコード例を次に示します。
fscanf(fp, "MaterialGroups = %d\n", &nMatrs); // 材質の数を読み込み
fscanf(fp, "TotalVertices = %d\n", &nVertices); // 頂点の数を読み込み
fscanf(fp, "TotalFaces = %d\n", &nFaces); // 面の数を読み込み
if (nMatrs == 0) // 材質の数が0なら例外
throw;
m_cMatGroups = nMatrs; // 材質の数をメンバ変数にコピー
m_cVertices = nVertices; // 頂点の数をメンバ変数にコピー
// 材質グループごとの情報の領域を確保
m_pmgMatGroups = __gc new MATERIALGROUP[nMatrs];
// 頂点情報の記憶領域を確保
m_pvVertices = __gc new Direct3D::CustomVertex::PositionColoredTextured[nVertices];
頂点の情報および面の情報を読み込みます。頂点の情報および面の情報は、材質ごとにグループ化されています。頂点の情報および面の情報を読み込む処理は、材質の数だけ繰り返されます。頂点と面データの読み込みを行うコード例を次に示します。
for (m = 0, vidx = 0;m < nMatrs;m++) // 材質の数だけループ
{
// 材質の色の情報を読み込み
fscanf(fp, "DiffuseColor = %x\n", &diffuse);
// テクスチャのインデックスを読み込み (未使用)
fscanf(fp, "TextureIndex = %d\n", &k);
// 材質グループに含まれる頂点の数を読み込み
fscanf(fp, "Vertices = %d\n", &nV);
m_pmgMatGroups[m].cLocalVertices = nV; // 頂点の数を保存
m_pmgMatGroups[m].diffuse = diffuse; // 材質の色の情報を保存
m_pmgMatGroups[m].idxFirstVertex = vidx; // 先頭の頂点のインデックスを保存
for (i = 0;i < nV;i++) // 頂点の数を保存
{
fscanf(fp, "%d = %f , %f , %f , %f , %f \n", &k,
&(m_pvVertices[vidx].X),
&(m_pvVertices[vidx].Y),
&(m_pvVertices[vidx].Z),
&(m_pvVertices[vidx].Tu),
&(m_pvVertices[vidx].Tv)
); // 頂点の情報を読み込み
++vidx; // 頂点のインデックスをインクリメント
}
fscanf(fp, "Faces = %d \n", &nF); // 面の数を読み込み
m_pmgMatGroups[m].cFaces = nF; // 面の数を保存
// 面の情報を保存する領域を確保
m_pmgMatGroups[m].pfceFaces = __gc new FACE[nF];
for (i = 0;i < nF;i++) // 面の数だけループ
{
fscanf(fp, "%d = %d , %d , %d \n", &k,
&face.a, &face.b, &face.c); // 面の情報を読み込み
m_pmgMatGroups[m].pfceFaces[i] = face; // 面の情報を保存
m_pvVertices[ face.a ].Color = m_pvVertices[ face.b ].Color =
m_pvVertices[ face.c ].Color = diffuse; // 頂点情報の中に色の情報をコピー
}
}
本章で作成するシューティングゲームでは、各クラスがそれぞれのキャラクター管理を行います。キャラクター管理については、第 2 章を参照してください。各クラスがそれぞれのキャラクターを管理することにより、キャラクターを分散して管理できるため、容易にプログラムの設計を行うことができます。また、他のクラスからキャラクター管理の処理を行うメソッドを呼び出すことにより、複雑なゲームの処理を実現しています。例えば、プレイヤーの機体を管理する CShipクラスは、弾を管理する CShots クラスの AddPlayerShot メソッドを呼び出すことにより、弾を撃つことができます。
キャラクターの動作のコード例では、ゲームを構成する 5 つのクラスの中でも、CEnemies クラスのキャラクターの動作処理について説明します。
敵キャラクターは、キャラクターの位置を変化させることにより移動します。CEnemies クラスのキャラクターの動作処理でも、キャラクターの位置を徐々に変化させることにより移動します。敵キャラクターを移動するコード例を次に示します。
// 敵キャラクターの速度を設定
m_nodes[i].m_vV.Z = -0.16f;
m_nodes[i].m_vV.X = (float)(cos((double)m_nodes[i].m_iFramecount / 18.0)
+ sin((double)m_nodes[i].m_iFramecount / 5.0)*0.5) * 0.2f;
// 位置ベクトルに速度ベクトルを足す
m_nodes[i].m_vPosition = m_nodes[i].m_vPosition + m_nodes[i].m_vV;
敵キャラクターは、プレイヤーの機体への攻撃を行います。プレイヤーの機体への攻撃を行うためには、CShots クラスの AddEnemyNormalShot メソッドを使用します。敵キャラクターがプレイヤーの機体への攻撃を行うコード例を次へ示します。
// 一定の間隔で弾を撃つ
if (!(m_nodes[i].m_iFramecount % 3) && ((m_nodes[i].m_iFramecount % 50) > 15))
pShots->AddEnemyNormalShot(m_nodes[i].m_vPosition.X,
m_nodes[i].m_vPosition.Y, m_nodes[i].m_vPosition.Z,
pShip->GetPosition());
CEnemies クラスのキャラクターの動作処理では、CShots クラスの HitTest メソッドを使用することにより、プレイヤーの弾との衝突判定を行います。プレイヤーの弾との衝突判定を行うことにより、敵キャラクターが攻撃を受けたかどうかを判断します。一定の回数の攻撃を受けた敵キャラクターは、倒されたことになり、画面上から削除されます。敵キャラクターが倒された場合、CBlast クラスの Add メソッドを呼び出すことにより、爆発の特殊効果を画面上に追加します。衝突判定から爆発の特殊効果を画面上に追加するまでの一連の処理を行うコード例を次に示します。
// プレイヤーの弾との衝突判定を行う
if (pShots->HitTest(m_nodes[i].m_vPosition.X,
m_nodes[i].m_vPosition.Z, 1.0f, 0, true))
{
// 一定数のダメージを受けた場合
if (--m_nodes[i].m_cDamage == 0)
{
m_nodes[i].m_bActive = false; // 敵キャラクターを削除
// 爆発の特殊効果を追加
for (k = 0;k < 4;k++)
pBlast->Add( ((rand()%21) - 10)/5.0f + m_nodes[i].m_vPosition.X ,
((rand()%21) - 10)/5.0f + m_nodes[i].m_vPosition.Y ,
((rand()%21) - 10)/5.0f + m_nodes[i].m_vPosition.Z, -k*4);
}
}
DirectX には、モデルを描画するためのルーチンが用意されています。しかし、DirectX に用意されているモデルの描画ルーチンは、X ファイル形式にのみ対応した仕様になっています。そのため、本章で作成する 3D シューティングゲームは、高速化のために、モデルに独自のデータ形式を使用しています。そこで、独自のデータ形式に対応したモデルの描画ルーチンを作成する必要があります。モデルの描画ルーチンは、DirectX に用意されているポリゴン描画メソッドを使用することにより、容易に作成できます。
第 6.2.2 項で述べたように、本章で作成する 3D シューティングゲームのモデルのデータ形式は、頂点および面の情報を材質ごとにグループ化しています。そのため、描画処理も材質のグループごとに行います。
本章で作成する 3D シューティングゲームでは、DirectX のポリゴン描画メソッドが直接使用できる形式で、モデルのデータをファイルに保持しています。そのため、ポリゴン描画メソッドは、データの形式を変更せずに、読み込まれたモデルデータをそのまま、頂点情報およびインデックス情報として使用します。モデルの描画を行うコード例を次に示します。
for (i = 0;i < m_cMatGroups;i++) // 材質の数だけループ
{
pDevice->DrawIndexedUserPrimitives(Direct3D::PrimitiveType::TriangleList,
m_pmgMatGroups[i].idxFirstVertex,
m_pmgMatGroups[i].cLocalVertices,
m_pmgMatGroups[i].cFaces,
m_pmgMatGroups[i].pfceFaces,
true,
m_pvVertices); // ポリゴンを描画
}
ビルボードとは、2D の画像をテクスチャとしてポリゴンに貼り、3D シーン上に合成する処理です。一般的に、ビルボード処理は、3D モデルの描画による表現が難しく、視点の移動による見た目の変化が少ない物体を描画する場合に適しています。例えば、ビルボード処理に適している物体として、爆発や煙などの特殊効果や、木や岩などの自然物があります。また、ビルボード処理によって表現された画像は、常にプレイヤーに向かって正面に表示されます。そのため、ビルボード処理によって表現された画像は、プレイヤーにとって、実際の 3D モデルと同様に見えます。ビルボードのイメージを図 3 に示します。
図 3 ビルボードのイメージ
ビルボード描画の処理では、画像を貼り付けたポリゴンを常にプレイヤーの正面に向けて描画します。ビルボード描画の処理について次に説明します。
ビルボード描画の処理では、ポリゴンを常にプレイヤーの正面に向ける必要があります。ポリゴンを常にプレイヤーの正面に向けるためには、ポリゴンの各頂点に対して、ビュー変換による回転を打ち消す変換を行う必要があります。ビュー変換による回転を打ち消すための変換を行う行列を生成するコード例を次に示します。
// 描画に使用する頂点情報
Direct3D::CustomVertex::PositionColoredTextured vertices[]
= new Direct3D::CustomVertex::PositionColoredTextured __gc [4];
Matrix matView = pDevice->Transform->View; // ビュー変換の行列を取得
matView.M41 = matView.M42 = matView.M43 = 0; // 回転成分を無効にする
matView.Invert(); // 逆行列にする
Vector4 v; // 座標変換に使用する 4 次元ベクトル
ポリゴンの各頂点の座標を設定では、ポリゴンを常にプレイヤーの正面に向けるために、ビュー変換を打ち消す行列を使用して、ポリゴンの各頂点の座標を設定します。ポリゴンの各頂点の座標を設定するコード例を次に示します。
// 1 つめの頂点情報の設定
v.X = -fSize;
v.Y = fSize;
v.Z = 0;
v.W = 1.0f;
v.Transform(matView);
vertices[0].X = v.X + pNode->m_vPosition.X;
vertices[0].Y = v.Y + pNode->m_vPosition.Y;
vertices[0].Z = v.Z + pNode->m_vPosition.Z;
vertices[0].Tu = 0.0f;
vertices[0].Tv = 0.0f;
// 2 つめの頂点情報の設定
v.X = fSize;
v.Y = fSize;
v.Z = 0;
v.Transform(matView);
vertices[1].X = v.X + pNode->m_vPosition.X;
vertices[1].Y = v.Y + pNode->m_vPosition.Y;
vertices[1].Z = v.Z + pNode->m_vPosition.Z;
vertices[1].Tu = 1.0f;
vertices[1].Tv = 0.0f;
// 3 つめの頂点情報の設定
v.X = -fSize;
v.Y = -fSize;
v.Z = 0;
v.Transform(matView);
vertices[2].X = v.X + pNode->m_vPosition.X;
vertices[2].Y = v.Y + pNode->m_vPosition.Y;
vertices[2].Z = v.Z + pNode->m_vPosition.Z;
vertices[2].Tu = 0.0f;
vertices[2].Tv = 1.0f;
// 4 つめの頂点情報の設定
v.X = fSize;
v.Y = -fSize;
v.Z = 0;
v.Transform(matView);
vertices[3].X = v.X + pNode->m_vPosition.X;
vertices[3].Y = v.Y + pNode->m_vPosition.Y;
vertices[3].Z = v.Z + pNode->m_vPosition.Z;
vertices[3].Tu = 1.0f;
vertices[3].Tv = 1.0f;
ビルボードの描画には、通常のポリゴン描画と同様の描画処理を使用します。描画処理を行うコード例を次に示します。
// 描画処理
pDevice->SetTexture(0, m_pTexture); // テクスチャ有効
pDevice->DrawUserPrimitives( Direct3D::PrimitiveType::TriangleStrip,
2,
vertices); // ポリゴン描画
本章では、第 5 章と第 6 章を通して、3D シューティングゲームのサンプルプログラムの実装方法について説明しました。具体的には、サンプルプログラムの開発に必要な衝突アルゴリズム、キャラクターの描画や背景の描画などについて説明しました。また、本コラムの総まとめとして、実際に第 5 章と第 6 章の内容を基に DirectX と Visual C++ .NET を利用して 3D シューティングゲームの開発を行いました。
ゲーム開発に DirectX を利用することにより、ゲームプログラミング初心者でも簡単に、高品質なゲームを開発できます。また、本コラムの内容を応用することにより、3D シューティングゲームだけでなく、アクションゲームなどの様々な種類のゲームを開発できます。
本連載は、C++ 言語と DirectX の基礎的な知識がある方を対象としています。
1986 年 | 関西大学工学部土木工学科卒業 |
1988 年 | 関西大学大学院工学研究科 土木工学専攻博士課程前期課程修了 |
1996 年 | 博士 (工学) 授与,関西大学 |
1997 年 | 関西大学総合情報学部助教授 (現在に至る) |
主な著書: | やさしい C のはじめかた,オーム社,1993 年 |
建設技術者のための知識情報処理の実践,関西大学出版部,1999 年 | |
DirectX8,工学社,2001 年 | |
ステップアップ XML,工学社,2002 年 | |
Linux アプリケーション入門,森北出版,2002年 ほか |
2001 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
2003 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 |
2003 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) |
主な著書: | Web 工房シリーズ Perl の達人,森北出版,1999 年 |
決定版 Visual Basic,共立出版,2000年 | |
DirectX8,工学社,2001 年 | |
Linux アプリケーション入門,森北出版,2002 年 | |
ステップアップ Visual C# .NET 入門,工学社,2002 年 ほか |
2000 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
主な著書: | DirectX8 & VC++ 3D の基礎とゲームの作り方,工学社,2002年 |
2000 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
2002 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 |
2002 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) |
主な著書: | Web 工房シリーズ Java の達人,森北出版,1999 年 |
デジカメ活用によるデジタル写真測量入門,森北出版,2000 年 | |
ステップアップ XML 活用法,工学社,2002 年 |
2002 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) |
主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |
2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) |
主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |