今回でこの講座は終わりです。先週作り始めたブロック崩し風ゲームを完成させましょう。
この手のゲームで必要になることは、ボールがブロックの当たったときにブロックの消す処理です。このサンプルプログラムではブロックを3個用意し、それぞれの当たりを判定する変数をblock1_hit, block2_hit, block3_hitとして0で初期化します。
ボールとブロックが衝突したらその変数を1に設定し、そのブロックを破壊し表示しないという処理を行っています。
// step6.cpp
int block1_hit = 0, block2_hit = 0, block3_hit = 0; // ブロックにボールが衝突すれば1、しないと0
/*** シミュレーションループ ***/
void simLoop(int pause)
{
int i, j;
dmWorldQuickStep(); // シミュレーションを1ステップ進める(高速版)
dmDraw(&ball);
dmDraw(&bar);
dmDraw(&field);
for (i =0; i < 3; i++)
{
dmDraw(&fence[i]);
}
// ブロックがボールに衝突していない場合はブロックを表示し
// ブロックがボールに衝突した場合はブロックを破壊する。
if (block1_hit==0) dmDraw(&block1);
else dmDestroyBox0(&block1);
if (block2_hit==0) dmDraw(&block2);
else dmDestroyBox0(&block2);
if (block3_hit==0) dmDraw(&block3);
else dmDestroyBox0(&block3);
STEPS++;
}
次に、今まで秘密のベールに包まれていたdm6.cppを説明します。このファイルでODEを簡単に使う関数を作っています。まず、2,3行目のexternで始まる変数の宣言は、dm6.cpp以外のファイルで宣言しているよとコンパイラーに教えています。これらの行がないとblock1, block1_hitが宣言されていないとエラーになり、externを付けないとstep6.cppでも宣言されているので2重定義でエラーになってしまいます。大きなプログラムは複数のファイルで構成されるのでこのexternは覚えておきましょう。
お次は、衝突検出を説明します。これはdm6.cpp内のnearCallback関数で行われています。if else文でブロック1のジオメトリ(block1.geom)、ブロック2のジオメトリ(block2.geom)、ブロック3のジオメトリ(block3.geom)がボールのジオメトリ(ball.geom)がo1, o2のどちらかなら衝突しているので、衝突判定用の変数block1_hit, block2_hit, block3_hitに1を代入しています。なお、ジオメトリとはODEで衝突判定に使われる物体の属性です。
また、nearCallback関数は2つの物体(正確にはジオメトリ)が接近して衝突しそうになったら自動で呼び出せれる関数です。このような関数をコールバック関数と呼びます。電話のコールバックをイメージしてください。引数のジオメトリo1, o2は衝突しそうな2つのジオメトリです。
このサンプルプログラムでたった3個のブロック崩し風ゲームは完成です。後は、いろいろ工夫して自分だけのゲームを完成させてください。
終わり。
// dm6.cpp
extern dmObject ball, field, fence[3], bar, block1, block2, block3;
extern int block1_hit, block2_hit, block3_hit;
/*** 衝突検出関数(コールバック関数) ***/
void nearCallback(void *data, dGeomID o1, dGeomID o2)
{
static const int N = 10; // 接触点数の最大値
dContact contact[N]; // 接触点
// 衝突情報の生成 nは衝突点数
int n = dCollide(o1,o2,N,&contact[0].geom,sizeof(dContact));
for (int i = 0; i < n; i++)
{
contact[i].surface.mode = dContactBounce; // 接触面の反発性を設定
// ボールとバーが接触したら反発係数を1.5に設定
if(((ball.geom == o1) && (bar.geom == o2)) || ((ball.geom == o2) && (bar.geom == o1)))
{
contact[i].surface.bounce = 1.5; // 反発係数(実際の世界では0.0から1.0)
}
// ボールと床が接触したら反発係数を0.5に設定
else if (((ball.geom == o1) && (field.geom == o2)) || ((ball.geom == o2) && (field.geom == o1)))
{
contact[i].surface.bounce = 0.5;
}
// ボールとブロック1が接触したら。block1_hitを1に設定
else if (((ball.geom == o1) && (block1.geom == o2)) || ((ball.geom == o2) && (block1.geom == o1)))
{
block1_hit = 1;
}
// ボールとブロック2が接触したら。block2_hitを1に設定
else if (((ball.geom == o1) && (block2.geom == o2)) || ((ball.geom == o2) && (block2.geom == o1)))
{
block2_hit = 1;
}
// ボールとブロック3が接触したら。block2_hitを1に設定
else if (((ball.geom == o1) && (block3.geom == o2)) || ((ball.geom == o2) && (block3.geom == o1)))
{
block3_hit = 1;
}
// その他は反発係数を0.8に設定
else
{
contact[i].surface.bounce = 0.8;
}
// contact[i].surface.bounce_vel = 0.05; // 反発に必要な最低速度
contact[i].surface.mu = 0.0;// dInfinity; // 摩擦係数
// 接触ジョイントの生成
dJointID c = dJointCreateContact(world,contactgroup,
&contact[i]);
// 接触している2つの剛体を接触ジョイントにより拘束
dJointAttach(c,dGeomGetBody(contact[i].geom.g1),
dGeomGetBody(contact[i].geom.g2));
}
}
ホームワーク
- step6-120119.zipをダウンロードして実行しよう!
- ブロックの数を1個増やそう。
- 3で作ったブロックの色を黒に変更しよう。
- ブロックの数を増やし、競技盤の半分ぐらいは埋め尽くそう。配列を使うのが望ましい。
- ボールの初期位置と飛び出す方向をランダムに変えよう。
- バーが競技盤を飛び出してしまいます。飛び出さないようにしよう。
- 得点を表示する機能を付けよう。標準出力にprintfでもかまいません。
- いろいろ工夫して自分だけのブロック崩し風ゲームを作ろう!遊ぶためにゲームの説明を文字ウインドウに表示しよう。
コメント