物理エンジンODEで学ぶC言語 [STEP6: まとめ]

ブロック崩し風ゲーム。未完成。続きを作ろう!

今回でこの講座は終わりです。先週作り始めたブロック崩し風ゲームを完成させましょう。

この手のゲームで必要になることは、ボールがブロックの当たったときにブロックの消す処理です。このサンプルプログラムではブロックを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));
    }
}

ホームワーク

  1. step6-120119.zipをダウンロードして実行しよう!
  2. ブロックの数を1個増やそう。
  3. 3で作ったブロックの色を黒に変更しよう。
  4. ブロックの数を増やし、競技盤の半分ぐらいは埋め尽くそう。配列を使うのが望ましい。
  5. ボールの初期位置と飛び出す方向をランダムに変えよう。
  6. バーが競技盤を飛び出してしまいます。飛び出さないようにしよう。
  7. 得点を表示する機能を付けよう。標準出力にprintfでもかまいません。
  8. いろいろ工夫して自分だけのブロック崩し風ゲームを作ろう!遊ぶためにゲームの説明を文字ウインドウに表示しよう。

コメントを残す

メールアドレスが公開されることはありません。