2014年03月30日

Bulletで剛体やるよ

テトさんの復旧作業をする予定でしたが、やる気が全く起きないので先にIrrlichtへのBulletの実装をやってます。
最近予定が狂いまくってて申し訳ない感じです。


▼Bulletの剛体

ではIrrlicht+Bulletの進捗報告。今回はかなり捗ったので紹介することが多いですが、まずは剛体から。
手始めにBulletにある3種類の剛体をゲームに組み込みました。

・静的剛体
・動的剛体
・キネマティック剛体

静的剛体というのはまったく動かない剛体で、BGEで言うところのStaticに相当するものですね。
動的剛体は、MMDでスカートや髪など揺れる物体に使われる剛体だと思われます。
わりとよく見かける一般的?な剛体ですね。
キネマティック剛体は、MMDで頭や胴体などに使われる剛体と似た(同じかも)挙動で、動的剛体を跳ね返すだけのものです。
この剛体を地面や壁に衝突させることはできません。
ゲームのキャラクターの当たり判定には使えませんね。
(ゲーム用の当たり判定は後述のゴーストオブジェクトを使うようです)

この三つの剛体についてはチュートリアルがありますので、特に問題もなく組み込めしました。
BulletをIrrlichtに組み込む方法は、フォーラムにサンプルがあったので、それを参考にしました。
サンプルの出所は忘れてしまいましたが、確かこの辺↓だったと思います。

http://irrlicht.sourceforge.net/forum/viewtopic.php?t=39007

ゲームで使う予定の剛体は静的剛体だけなので、剛体についてはあまり深入りはしないつもりです。
衝突判定フィルタリングは実装したいですが、拘束まではやらないかも。
MMD風クロスの再現は興味があるのでもしかしたらやるかもないけど、意外と物理演算は処理が重いのでゲームには採用できないかもしれず、やるとしても遊びかネタ用って感じになりそうです。


▼キャラクターの当たり判定

3Dゲームを作るうえで最も重要なのは、何と言ってもキャラクターの当たり判定。
Irrlichtにも衝突判定の仕組みはありますが、あまり評判が良くないです。
実際、サンプルで遊んでいるときも、引っ掛かって動けなくなったりしましたし。
それがBulletを使うことにした主な理由ですね。

Bulletには、衝突判定と押し戻し処理を提供する「キネマティックキャラクターコントローラ」というシステムがあります。
このシステムを持ったオブジェクトは、プレイヤーが操作することが可能で、さらに重力や慣性といったものの影響を受けずに剛体に接触できます。
地面の上を歩かせたり壁をズリズリしたりするには必須な仕組みですね。

このコントローラのベースとなるのはゴーストオブジェクト。
ゴーストオブジェクトには衝突判定しかなく、物との接触はできません。
そこでペアキャッシュをオーバーラップして、ゴーストを含むペアがあった場合は剛体と別の処理を実行するようにします。
その処理というのが、いわゆる「押し出し処理」と呼ばれるものです。
こうすることで、Bulletの物理演算の影響を受けずに当たり判定処理をしていると思われます。

なお、この辺は全て「たぶん」がつきますのでご注意ください。
私は今のところそんな感じで消化しておりますが、間違ってたらごめんなさいです。

で、今回はこのキネマティックキャラクターコントローラを作成するところまで完了しました。
これを作るにあたって、「IrrBullet」というIrrlicht用のBulletのラッパーライブラリにあるサンプル「Character」のソースコードを大いに参考にさせていただきました。
このサンプルがなかったら当たり判定は実装できなかったと断言できます笑
ていうか自力で分かるわけないです、こんなの。


▼本編

今回はキネマティックキャラクターコントローラのBulletでの実装方法を紹介します。
はじめに断っておきますと、キネマティックキャラクターコントローラは、IrrBulletを使えば簡単に実装することができます。
私も最初はIrrBulletで済ませるつもりでした。
しかし、私の環境でIrrBulletを導入してゲームをビルドすると、何かは忘れましたが競合エラーが出てビルドできませんでした。
コンパイラ周りについて詳しくない私はすぐに諦めましたが、ビルドが通るならIrrBulletを使うことをおすすめします。

では本題。
これから紹介するのは自前のBulletのコードの一部です。全文紹介してるととんでもない量になるので、個人的にハマったとこだけ紹介します。
基本的にはIrrBulletと同じ仕組みで、なおかつIrrBulletのサンプル「Character」のソースコードをそのまま引用したものが多いですが、ところどころ微妙に違うので参考される場合は注意してください。
もっと詳細なコードが見たい方はサンプルのソースを解析してくださいませ(丸投げ

それでは、コードの紹介。

cconfg = new btDefaultCollisionConfiguration();
broadphase = new btDbvtBroadphase();
broadphase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback());
dispatcher = new btCollisionDispatcher(cconfg);
solver = new btSequentialImpulseConstraintSolver();
world = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,cconfg);

では簡単に解説。
ここでは力学ワールドを作成しています。
IrrBulletのサンプルから必要な箇所を抜き取っただけのものです。

ここで重要なのは、3行目のペアキャッシュをオーバーラップをしている部分で、この記述がないとゴーストを地面に立たせることができません。
最初はここに気付かなくてかなりハマりました。
それ以外は普通の初期化となっております。

ちなみに1行目で、

btDefaultCollisionConfiguration

となっている部分、IrrBulletでは・・

btSoftBodyRigidBodyCollisionConfiguration

となっています。
これはビルドエラーを回避するためにこうしてあるのですが、エラーが出ない場合は名前が長い方を使うといいのかもしれません。
(再起動したらビルドは通りましたが、名前からしてソフトボディを使うためのものっぽいのでデフォルトのままにしてます)

ワールドを作成したら、次はゴーストオブジェクトを作成してワールドに投下します。
コードは以下のとおり。

btTransform startTransform;
startTransform.setIdentity();
startTransform.setOrigin(btVector3(0.210098,100.6433364,1.453260));
btPairCachingGhostObject* GhostObject = new btPairCachingGhostObject();
GhostObject->setWorldTransform(startTransform);
btScalar characterHeight = 6.0;
btScalar characterWidth = 4.95;
btCapsuleShape* Capsule = new btCapsuleShape(characterWidth,characterHeight);
GhostObject->setCollisionShape(Capsule);
GhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
btScalar stepHeight = btScalar(0.35);
btKinematicCharacterController* Character = new btKinematicCharacterController(GhostObject, Capsule,stepHeight);
world->addCollisionObject(GhostObject,btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::StaticFilter|btBroadphaseProxy::DefaultFilter);
world->addAction(Character);

この辺りはもろに借り物のコードなので貼るのは気が引けるのですが、たぶん需要はあると思うので構わず貼ってしまいます。
一応、必要な部分だけをまとめたオリジナルのコードではあるんだけどね・・

それはさておき、解説。
ここでは衝突形状とゴーストオブジェクトを作成して関連付け、それをbtKinematicCharacterControllerに渡して、力学ワールドに投下しています。
って、コードをそのまま読んでるだけで解説でもなんでもないですが・・・

startTransformは、衝突形状の原点の初期位置です。
BlenderやMMDを触っている方なら分かると思いますが、衝突形状は、BlenderのCollisionBounds、MMDの剛体に相当するもので原点はジオメトリの中心になります。
あれをイメージしながらcharacterHeight、characterWidthを調整していくと良いかと思います。

そしてこの後に(先でもいいけど)Irrlichtでキャラクターのノードを作成し、次いでメインループのなかでゴーストオブジェクトの動きをキャラクターノードに反映させるという処理をしていくという流れになります。

キャラクターノードの作成については特に変更はありませんので省略するとして、メインループ内の処理について、私が実際に使ってるコードを貼っておきます。
イベント処理や変数の宣言などはカットしていますのでご注意ください。

//キーボードイベントの処理
if(receiver.IsKeyDown(KEY_KEY_W)) posz = -1.0f;
else if(receiver.IsKeyDown(KEY_KEY_S)) posz = 1.0f;
else posz = 0.0f;

if(receiver.IsKeyDown(KEY_KEY_A)) posx = -1.0f;
else if(receiver.IsKeyDown(KEY_KEY_D)) posx = 1.0f;
else posx = 0.0f;

//嘘ジャンプ。地面がなくても安心
if(receiver.IsKeyDown(KEY_SPACE)) posy = 3.5f;
else posy = -3.5f;

//力学ワールドでのキャラクターの位置を更新
Character->setWalkDirection(btVector3(posx,posy,posz));

//ゴーストオブジェクトの位置をノードに反映
matrix4 mat;
GhostObject->getWorldTransform().getOpenGLMatrix(mat.pointer());
sydney->setPosition(mat.getTranslation());

Irrlichtのマスコットキャラ、シドニーさんを使わせていただきました。
うちとしてはテトさんかケモ耳さんで紹介したいんだけど、まいいや。

それはさておきコード解説ですが、前半は特に説明の必要はないかと思います。
最後から2行目は、IrrBulletの・・

IKinematicCharacterController::getWorldTransform()

これの再現です。
このメソッドはBulletのbtTrasformを、Irrlichtの行列に変換します。

で、最後の行で変換した行列のうち平行移動行列をベクトルに変換してシドニーさんの新しい位置としています。

このときの注意点としては、移動速度をインクリメントしてはいけないということ。
setWalkDirection()に渡した値は内部でインクリメントされますので1.0fを渡すだけでOKです。


以上でソース紹介は終わりです。
最後に順番が前後しますが、必要になりそうなヘッダファイルも紹介しておきます。

#include <btBulletCollisionCommon.h>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/Gimpact/btGImpactCollisionAlgorithm.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <BulletDynamics/Character/btKinematicCharacterController.h>
#include <BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.h>
#include <BulletSoftBody/btSoftRigidDynamicsWorld.h>
#include <BulletSoftBody/btSoftSoftCollisionAlgorithm.h>



今回は以上です。
長々と書きましたが、ゲームが作りたいってだけの人はIrrBulletを使ったほうがいいです(Unityとは言わない
私もここまでやっておいて、やっぱりIrrBullet使いたくなってますw
先のことを思えば、このままBullet使ってくはしんどい気がしますね。。
頑張ってビルド通そうかな。

posted by gency at 00:12| Comment(0) | Bullet