Bulletでゲームを作ってみるPart1 舞台の準備

投稿日:

物理エンジンといえば、建物を破壊したりリアルな挙動を実現する派手なゲームを思い浮かべますが、その様なゲームでなくても十分物理エンジンを活用可能です。物理エンジンには、形状同士の衝突を検出する機能があり、多数の形状同士の衝突判定を高速に行うことができます。キャラクターの移動、攻撃判定、爆弾、イベントトリガなどを物理エンジンを利用すれば簡単かつ高速に判定することができます。使い方に少しコツがいるため判定処理の方法を中心に説明しながら、簡単な3Dゲームを作っていきます。


ゲーム仕様(仮)

TPS視点で障害物となる建物を銃撃や爆弾で崩しながら目的地を目指すゲームを作ってみます。操作するキャラクターのアクションは、移動、ジャンプ、銃撃、グレネード投擲など。建物を物理エンジンの剛体で構成し、爆弾などで崩しながらゴールを目指す。剛体につぶされる、谷やマップ外に落下で死亡、リスタート。ゴールについたら次のステージへ。破壊だけでなくパズルのような仕掛けも用意する(余裕があれば)。


ゲームでの物理エンジン

ゲームでの利用には主に3種類、物理エフェクト、物理シミュレーション、コリジョン判定などがあります。

物理エフェクト

髪の毛や服を揺らす、破壊したオブジェクトの破片の挙動など、見た目だけでゲームシステムには影響しない処理です。ゲームで映像のクオリティーを上げるためによく使われています。今回作成するゲームでは利用しない予定です。

物理シミュレーション

物理挙動をゲームシステムに組み込んだゲームでの利用方法です。今回作成するゲームでは建物や壁など障害物の物理挙動を物理エンジンの剛体を使って実現します。ほとんどのゲームの挙動は物理法則を無視したものが多いため、あまり使われないと思います。

コリジョン判定(衝突判定)

見た目が派手な物理エフェクトに目が行きがちですが、たぶん物理エンジンで最も利用されている機能だと思います。剛体などの衝突を検出する処理ですが、この機能だけでも利用できます。複雑な場面に対応できるよう設計されているため、高機能で高速です。キャラクターの移動、攻撃判定、イベントトリガなど応用範囲は広いです。


ゲーム舞台の準備(Bulletで利用できる部品)

ゲームの世界を構成する部品について簡単に説明していきます。

コリジョン形状 btCollisionShape

様々な形状のコリジョンを作成することができ、この形状を使って剛体の運動(衝突)やゲーム用判定を行います。Bulletで利用できる形状は、剛体のような運動(移動)することができる凸面(convex)と、動けないが任意の形状を表現できる凹面(concave)の2つに分けられます。

計算式で表現される凸面(convex)形状

球(btSphereShape)、カプセル(btCapsuleShape)、円柱(btCylinderShape)、円錐(btConeShape)
この4つは半径と高さの2つのパラメータで表現される形状で、完全な曲面を表現できるものはこれだけです。情報量が少なく、計算が簡単、転がる物体を表現可能です。ゲームの判定処理での使用頻度が高い形状だと思います。

頂点データで表現される形状(凸面多面体)

BOX(btBoxShape)、線、三角形、三角錐(btBU_Simplex1to4)、凸包(btConvexHullShape)
複数の頂点データで表現される形状です。btConvexHullShapeですべての凸面の多面体を表現可能です。ですが計算の効率化のため、各頂点を直角に限定したbtBoxShape、4頂点以下の形状(点、線、三角形、三角錐)を表現するbtBU_Simplex1to4などがあり、できるだけ効率化した形状を使用すれば計算負荷が軽くなります。

凸面三角形メッシュ(btConvexTriangleMeshShape)

三角形ポリゴンで凸面の多面体を表現します。へこんだり、穴の開いた形状を指定できますが、下図のように凸面の多面体の内部にある頂点(三角形)は無視されます(凸包)。ポリゴン数が多いと計算負荷が高くなるため、どうしても形状が表現できないときの最終手段です。

凹面三角形メッシュ(btBvhTriangleMeshShape)

任意の形状を表現できますが、剛体など移動するオブジェクトには使用できません。地面や複雑な建物のコリジョンなどで利用されることが多いと思います。凸面の形状と違い、三角形ポリゴンで構成されたハリボテで簡単にコリジョン抜けが発生してしまうため、利用には注意が必要です。凸面形状は内部に侵入しても外に押し出されます。

形状の組み合わせ(btCompoundShape)

移動するオブジェクトには凸面しか使用できませんが、凸面の形状を複数組み合わせることで、任意の形状を表現可能です。ただし、複雑にし過ぎると物理計算が不安定になります。ご利用は計画的に。


Bulletでの作成

各形状を作成するソースコードです。三角形メッシュの形状を作成する際は注意が必要です。コンストラクタに渡すメッシュ管理クラスのポインタを自分で保持し、利用が終わったら自分で削除する必要があります。コンストラクタ終了後に削除すると強制終了。Bulletはユーザーが設定するポインタの削除を実行しないので注意。自分で参照管理、リソース管理が必要です。

//BOX
  btBoxShape* shape = new btBoxShape(btVector3(w/2,h/2,d/2));

// 球
  btSphereShape* shape = new btSphereShape(radius);//半径
  
// カプセル 球+円柱+球
  btCapsuleShape* shape = new btCapsuleShape(radius,height);//半径,円柱高さ

// 円柱
  btCylinderShape* shape = new btCylinderShape(btVector3(radius, height, radius));//半径,高さ

// 円錐
  btConeShape* shape = new btConeShape(radius, height);//半径,高さ

// 三角錐
  btVector3 vertex[4] ={
     0,0,1.5f },{ -1.5f,0,-1.5f },{ 1.5f,0,-1.5f },{ 0,2,0 },
  };
  btBU_Simplex1to4* shape = new btBU_Simplex1to4(vertex[0], vertex[1], vertex[2], vertex[3]);
  
  // 三角形
  btBU_Simplex1to4* shape = new btBU_Simplex1to4(vertex[0], vertex[1], vertex[2]);
  // 線
  btBU_Simplex1to4* shape = new btBU_Simplex1to4(vertex[0], vertex[1]);
  // 点
  btBU_Simplex1to4* shape = new btBU_Simplex1to4(vertex[0]);

// convex hull 凸包
  btScalar vertex[] = { Vx1,Vy1,Vz1,,,,};
  btConvexHullShape* shape = new btConvexHullShape(
				(btScalar*)vertex, vertex_num, sizeof(btScalar)*3);

//convex triangle 凸面三角形
  //メッシュ情報を記録するクラス
  btTriangleMesh* triangle_mesh = new btTriangleMesh();

  // 頂点登録
  btScalar vertex[] = { Vx1,Vy1,Vz1,,,,};
  int index[] = {0,1,2, ,,,,, }; //三角形リスト
  for(int i=0; i<vertex_num; ++i){
    const btScalar* vf = &vertex[i*3];
    btVector3 v(vf[0], vf[1], vf[2]);
    triangle_mesh->findOrAddVertex(v, false);
  }
  // Index登録
  for(int i=0; i<index_num; i+=3){
    triangle_mesh->addTriangleIndices(index[i], index[i+1], index[i+2]);
  }

  //引数で渡すbtTriangleMesh*は保持しておく、内部でコピーさ入れない
  // Bullet内部処理で削除されないため、使用後は自分でdelete
  btCollisionShape* shape = new btConvexTriangleMeshShape(triangle_mesh);

//concave triangle 凹面三角形
  //メッシュ情報を記録するクラス
  btTriangleMesh* triangle_mesh = new btTriangleMesh();

  (略)
  
  //引数で渡すbtTriangleMesh*は保持しておく、内部でコピーさ入れない
  // Bullet内部処理で削除されないため、使用後は自分でdelete
  btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(triangle_mesh);

// 形状の組み合わせ
  btCylinderShape* board_shape = new btCylinderShape(btVector3(10, 1, 10));
  btCylinderShape* leg_shape = new btCylinderShape(btVector3(1, 2, 1));

  // 形状の組み合わせ
  btCompoundShape* comp_shape = new btCompoundShape;

  // ローカル姿勢を指定してbtCompoundShapeに追加
  btTransform local = btTransform::getIdentity();
  local.setOrigin(btVector3(0, 2, 0));
  comp_shape->addChildShape(local, board_shape);

  local.setOrigin(btVector3(-5, 0, -5));
  comp_shape->addChildShape(local, leg_shape);
  local.setOrigin(btVector3(5, 0, 5));
  comp_shape->addChildShape(local, leg_shape);
  local.setOrigin(btVector3(5, 0, -5));
  comp_shape->addChildShape(local, leg_shape);
  local.setOrigin(btVector3(-5, 0, 5));
  comp_shape->addChildShape(local, leg_shape);
  // 設定したポインタはBulletが削除しないため、使用後自分でdelete


コリジョンオブジェクト btCollisionObject

位置情報とコリジョン形状を使って、物理ーワールドでのコリジョン判定を行う実体(オブジェクト)です。剛体(btRigidBody)だけでなく、判定処理専用のゴースト(btGhostObject)というオブジェクトがあります。

剛体(btRigidBody)

剛体を物理法則に従って移動させるコリジョンオブジェクトです。特に説明は不要だと思います。質量を0にすると移動しない完全に静止した剛体になります。

  
  btCollisionShape* shape = 剛体の形状;
  btScalar mass = 質量;

  // 移動するオブジェクトかどうか
  bool is_dynamic = (mass != 0.0f);

  // 慣性モーメント
  btVector3 inertia(0, 0, 0);
  if(is_dynamic) {
    shape->calculateLocalInertia(mass, inertia);
  }

  // 剛体操作
  // 表示用剛体位置の取得
  btDefaultMotionState* mot = new btDefaultMotionState(transform);

  // 剛体作成
  btRigidBody::btRigidBodyConstructionInfo rb_info(mass, mot, shape, inertia);
  btRigidBody* rigid = new btRigidBody(rb_info);
  
  // 物理ワールドに追加
  world->addRigidBody(rigid);
   // 設定したポインタはBulletが削除しないため、使用後自分でdelete
ゴースト(btGhostObject)

剛体などの他のコリジョンオブジェクトから影響を受けず、衝突状態だけを検出するオブジェクトです。衝突情報を使用しキャラクターの移動など各種判定を行います。ゲームプログラムでの主役といっていいと思います。設定によって、剛体を跳ね返すか通過させるか選べます。跳ね返す場合、静止した(質量0)剛体とほぼ同じ挙動ですが、ぶつかった剛体を検出することができます。さらに詳しい衝突状態を効率よく取得できるbtPairCachingGhostObjectという派生クラスもあります。

  btCollisionShape* shape = ゴーストの形状;

  btGhostObject* ghost = new btGhostObject;

  // 形状設定
  ghost->setCollisionShape(shape);

  // 他のオブジェクトへの反応なし
  // CF_NO_CONTACT_RESPONSEを指定しないと剛体と衝突する
  ghost->setCollisionFlags(  ghost->getCollisionFlags()
                           | btCollisionObject::CF_NO_CONTACT_RESPONSE);

  // 位置設定
  btTransform t(rot0, btVector3(0, 30, 25));
  ghost->setWorldTransform(t);
 
  // 物理ワールドに追加
  world->addCollisionObject(torushole_ghost);
  // 設定したポインタはBulletが削除しないため、使用後自分でdelete
  

拘束(Constraint)

剛体と剛体を接続する機能です。ばねや蝶番(ヒンジ)などいろいろな種類が用意されています。ゲーム処理自体ではあまり使用することはないと思いますが、物理エフェクトでよく利用されます。

  //2点間接続(ボールジョイント)
  btPoint2PointConstraint* constraint = new btPoint2PointConstraint(
                   rigid_a, rigid_b, btVector3(0, 51, 10), btVector3(0, -1, 10));
                   
  world->addConstraint(constraint);

アクションインターフェイス(btActionInterface)

stepSimuration関数内から呼ばれるコールバック(仮想関数)を定義するクラスで、主にゴーストを使った判定を行うためのクラスです。ゴーストを使った判定をBulletのstepSimuration関数外でも実行できますが、btActionInterfaceを使用したほうが、より正確な判定処理や結果の反映が可能です。

//ゴーストを利用した判定処理
class MyAction  : public btActionInterface
{
public:
  //Bulletから呼び出される関数
  virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep)
  {
    if(!pGhost)return;
    int num = pGhost->getNumOverlappingObjects();

    for(int i=0; i<num; ++i){
      // 重なっているコリジョンオブジェクト(剛体など)
      btCollisionObject* obj = pGhost->getOverlappingObject(i);
      
      // 判定処理など
    }
  }

  virtual void debugDraw(btIDebugDraw* debugDrawer)
  {
    // デバッグ描画
  }

private:
  btGhostObject* pGhost;//判定用ゴースト
};

//world->addAction()で追加
//stepSimuration関数内部からupdateAction関数が呼ばれる

サンプルゲーム(ちゃぶ台返し)

これまで紹介した機能をすべて使って、簡単なゲームを作ってみました。マウスドラッグでちゃぶ台返しをひっくり返し、上に乗ったものが空中にある輪っかを通過したらポイント獲得、という簡単なゲームです。輪っか通過の判定にゴーストとアクションインターフェイスを利用しています。輪っかにゴースト(球)を配置、重なった剛体の動きを監視して通過したらポイント加算と輪っかを回転させています。

ソースコード

ちゃぶ台返しゲームのソースコードです。今後のゲーム作成に対応できるように少し複雑になっているので、Bulletの処理だけを抜き出した簡易バージョン(TutorialBulletShapeプロジェクト)を作成しています。Bullet使用の参考にしてください。Bulletライブラリのソースコード同梱なので、Bulletライブラリの環境構築は不要です。

“Bulletでゲームを作ってみるPart1 舞台の準備” をダウンロード DX11BulletGame1.zip – 268 回のダウンロード – 1 MB


Bulletでゲームを作ってみる

Bulletでゲームを作ってみるPart1 舞台の準備
Bulletでゲームを作ってみるPart2 キャラ移動の準備
Bulletでゲームを作ってみるPart3 キャラの移動

「Bulletでゲームを作ってみるPart1 舞台の準備」への2件のフィードバック

コメントを残す

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

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.