ページ 11

Box2Dに関してPart2

Posted: 2010年6月07日(月) 11:59
by るー
以前Box2Dに関して質問させて頂いた者です。
Justyさんをはじめ、お答え下さった方々に改めてお礼を言いたいです。
ありがとうございました。

あれからもう1度Box2Dを復習していたのですが、
やはり分からないところが出てきたので質問させて下さい。

プログラムを添付したので見て下さい。
まずブロックが3個出ているプログラムからです。
ブロックを3個積み上げたらクリアするという簡単なプログラムを作りたいのですが、
この場合、衝突検出用のコールバック関数にあるResult関数で衝突情報を保存し、
その衝突情報をメインループ内にある「// 接触のリストに対する処理」のところで
プログラム処理を書いていけばいいのでしょうか?
そうであれば書く場合にはどうすればいいのですか?
そもそもこの考え方が合っているのでしょうか?

考え方が合っていると仮定して、今度はブロックが5個出ているプログラムです。
こちらもブロック3個の時と同じく、ブロックを3個積み上げたらクリアという条件です。
ブロックが5個ということは、例えば片方に2個ブロックを積み上げ、もう片方に3個積み上げたい場合、
2個積み上げたときはまだクリアになりません。ですが、残りの3個のブロックで3個積み上げるとき、
1個積み上げた瞬間にクリアになってしまうと思います。
これを回避したいときはどうすればいいでしょうか。
また、2個くっついてるブロックの上にブロックを置いた(漢字でいう「品」)状態のとき、
さらに1個ブロックを積んだらクリアになるのでしょうか?

長文ですいませんが、アドバイスを頂きたいです。

Re:Box2Dに関してPart2

Posted: 2010年6月08日(火) 09:51
by るー
上のプログラムにピクチャーが入っていなかったので、
入れ直しました。
こちらのプログラムを見てください。

Re:Box2Dに関してPart2

Posted: 2010年6月08日(火) 19:46
by Justy
>その衝突情報をメインループ内にある「// 接触のリストに対する処理」のところで
>プログラム処理を書いていけばいいのでしょうか?
>そうであれば書く場合にはどうすればいいのですか?

 アプローチとしては間違っていないと思います。

 ただどう書くかはどういう情報をどう保存したのか、何をするのかによって
変わってきますのでなんとも言えません。



>これを回避したいときはどうすればいいでしょうか。

 前者を回避、というかどうしてそうなるのか今ひとつわかりませんが、
各ボックスが地面に与える力を調べて、ボックス3つ分の力が1つのボックスから
(安定して)かかっているものを調べれば出来そうです。

 しかし。


>また、2個くっついてるブロックの上にブロックを置いた(漢字でいう「品」)状態のとき、
>さらに1個ブロックを積んだらクリアになるのでしょうか?

 この後者もクリアにしたいなら、この方法ではだめですね。
 多分次の条件を満たすように書けばできるような気がしますけどどうでしょうね。

1) 地面の上に直接載っているボックスをリストアップ
2) それらのボックスが接触している別のボックスを順繰りに辿りつつ
 片方が片方の上に載っている(位置情報だけで判定)ものを見つけ、
 それが3つ以上連なっているものを探す
3) マウスのイベントがなく一定時間安定して同じ 2)が見つかったらクリア

Re:Box2Dに関してPart2

Posted: 2010年6月09日(水) 09:55
by るー
>ただどう書くかはどういう情報をどう保存したのか、何をするのかによって
変わってきますのでなんとも言えません。

●例えば、ブロックが接触した時、
 接触されたブロックの方を消すプログラムの場合、

// 1フレーム内に発生した接触情報を保存しておく配列
#define CONTACTLIST_MAX 100 // 配列の数 1フレーム内に発生するであろう衝突の数以上準備しとかないとね
b2ContactResult ContactList[CONTACTLIST_MAX]; // 配列
int numContactList = 0; // 配列インデックス

// 接触を把握するためのコールバック関数
// このクラスをワールドのセットリスナーで登録することで、接触が発生した場合に自動的にコールする
// それぞれの関数ないにあたったときに行いたい処理を記述していく
class MyContactListener : public b2ContactListener
{
public:
// 接触開始
void Add(const b2ContactPoint* point)
{
}

// 接触中
void Persist(const b2ContactPoint* point)
{
}

// 接触終了
void Remove(const b2ContactPoint* point)
{
}

// 接触情報が発生した時に呼ばれる 一般的にはよばれるタイミングは接触中とおなじだと思う
void Result (const b2ContactResult *point)
{
DrawFormatString( 0, 20, GetColor( 255, 0, 0 ),"接触情報が発生" );

// ここでは、衝突した際に行う処理は、行えないし行わないほうがよい
// よって、ここでは衝突情報の保存のみを行い、実際の処理はメインループのほうでやる
if( numContactList < CONTACTLIST_MAX )
{
ContactList[numContactList] = *point; // 接触情報の保存
         numContactList++;           // 更新
}
}
};
// コンタクトリスナーの実体を作成
MyContactListener cListener;


~~~~~~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~~


// 接触のリストに対する処理
for( int i = 0; i < num; i++ )
{
// ボックスとボックスが衝突したら、衝突された方のブロックを消す
if( (int)ContactList.shape1->GetBody()->GetUserData() == USERDATA_FREEBOX &&
(int)ContactList.shape2->GetBody()->GetUserData() == USERDATA_FREEBOX )
{
// ボディが存在してるいるかどうか確認、静的か動的かのフラグが立つ
if( ContactList.shape2->GetBody()->IsDynamic() || ContactList.shape2->GetBody()->IsStatic() )
{
world->DestroyBody( ContactList.shape2->GetBody() ); // ボディの削除
}
}
}
num = 0; // インデックスリセット
.
.
.
.
.
書き加える部分はこんな感じですね。


地面と接触しているものをリストアップするというのは、
GetBodyList()を使うんですよね? 画像

Re:Box2Dに関してPart2

Posted: 2010年6月09日(水) 19:30
by Justy
>↑保存の仕方はこんな感じです。
 なるほど。
 
 まずこの衝突コールバック(Result)は b2Shape単位での衝突の結果を返してくるのですが
このプログラムにおける1つのボックス(b2Body)は2つの b2Shapeから出来ていますので
ボックス同士が接触すると2回呼ばれることがあります。

 そうするとこれをそのまま保存した b2Shapeレベルの接触情報リストのままでも
問題はないのですが、もう少し扱いやすくする為一度 b2Bodyレベルの接触リストに
変換した方がいいかと思います。

 実際前回こちらから出したコードではこの情報からもっと単純な接触した b2Bodyの組のリストを
作って連なりを調べていたはずです。
 たしかに一手間かかりますが、接触している b2Body同士を調べるのはかなり楽になるはずです。



>書き加える部分はこんな感じですね。
 なんとなくですが、だめな気がします。

 b2Bodyを破棄した後はその b2Bodyは無効になるわけですが、直後の ContactList.shape1->GetBody()が
その破棄した b2Bodyだった場合問題になりませんか?

 それに「ボディが存在してるいるかどうか確認」として b2Body::IsDynamicか b2Body::IsStaticなのかを
見ているのもよくわかりません(全ての b2Bodyは IsDynamicか IsStaticのどちらかしかないのでは?)。

 b2Bodyの安全に破棄をするのであれば、このループ内で「破棄する(重複無しの)b2Bodyリスト」を構築して
ループが終わったら改めてその破棄リストを元に破棄をするのがいいかと思います。



>地面と接触しているものをリストアップするというのは、
>GetBodyList()を使うんですよね?
 b2World::GetBodyList()のことですか?
 んー、使っても出来なくはないですが、面倒というか効率が悪いです。
 
 衝突コールバックで集めた衝突情報リスト(b2Shape / b2Bodyレベルのどちらであるかは別として)を
辿った方がいいでしょう(b2Bodyレベルの方が効率よく探せるのは言うまでもないですね)。

 或いは地面の b2Bodyのコンタクトリストか。
 コールバックで集めた衝突情報リストはワールド全体のであるのに対し、こちらの方は確実に接
触しているとは言えませんが、地面と接触している可能性があるものだけをピンポイントで辿れますので
処理的には早いかと思います。


 もう少し補足をしましょうか。

>>1) 地面の上に直接載っているボックスをリストアップ
>>2) それらのボックスが接触している別のボックスを順繰りに辿りつつ
>> 片方が片方の上に載っている(位置情報だけで判定)ものを見つけ、
>> それが3つ以上連なっているものを探す
 このあたりは結局前回のてんびんのときと似たような処理になるでしょう。

 前回ですと各バスケットを起点として GetMassOnBasket / GetTotalMassContactedBoxを呼び出し
順繰りにコンタクトリストを辿って、再帰しながらバスケット上に載っている物を調べていました。

 これが今回は地面の上に載っているボックスを起点として順繰りにコンタクトリストを辿って、
3つのボックスが位置関係的に「載っている」ものかどうかを調べていくことになります。

 今回は連なっている数を意識しながら3つ以上になった段階で処理を打ち切り
その3つのボックスの組み合わせを記録していく感じになるのではないでしょうか。

Re:Box2Dに関してPart2

Posted: 2010年6月10日(木) 11:05
by るー
>実際前回こちらから出したコードではこの情報からもっと単純な接触した b2Bodyの組のリストを
作って連なりを調べていたはずです。
 たしかに一手間かかりますが、接触している b2Body同士を調べるのはかなり楽になるはずです。

class DestructionListener : public b2DestructionListener
{
public:
DestructionListener(){}
void SayGoodbye(b2Shape* shape)
{
}

void SayGoodbye(b2Joint* joint)
{
}


private:

};

↑の部分のことでしょうか?違いますかね?すいません、よく分かりません。
正直私が相当苦手にしているのがコンタクトリスナーのプログラムの書き方ですね。
書き方も特殊ですし。


>b2Bodyを破棄した後はその b2Bodyは無効になるわけですが、直後の ContactList.shape1->GetBody()が
 その破棄した b2Bodyだった場合問題になりませんか?

 問題になりましたw
ContactList.shape1側をマウス操作して当てた場合は大丈夫ですが、
 ContactList.shape2側から当てた場合だと停止してしまいますね。

>これが今回は地面の上に載っているボックスを起点として順繰りにコンタクトリストを辿って、
 3つのボックスが位置関係的に「載っている」ものかどうかを調べていくことになります。

 ちょっと待って下さい。
 この「コンタクトリストを辿る」という部分を完全に理解したいので、
 もっと簡単な例を出してもいいですか?
 地面があり、ボックスが2個離れて地面の上に乗っているプログラムがあったとして、
 どちらかのボックスをもう一方のボックスに積んだら即クリアするというプログラムにする場合、
 考え方として、
 ①地面に接触しているボックスをコンタクトリスナーで探して、接触しているボックスを探す
 ②接触しているボックスがあったら、そのボックスからまたコンタクトリスナーで接触している
 ボックスを探す
 ③あったらクリアにする
 という考え方でいいのでしょうか?

 この考え方が合ってるかどうかはさておき、条件として「接触している」では駄目ですよね。
 特に②の場合、接触しているという条件だったらボックス横にくっついてる場合も考えられますし。
 うーん、難しいですね。

Re:Box2Dに関してPart2

Posted: 2010年6月10日(木) 14:29
by Justy
>↑の部分のことでしょうか?違いますかね?すいません、よく分かりません。
 そこはオブジェクトが破棄されるときに呼び出されるところです。

 該当する箇所は前回のてんびんでいうと MyContactListener::Persistと
MyContactListener::IsContactedBodiesです。

 前回のてんびんでは b2ContactResultの情報を必要としていなかったので、
MyContactListener::Resultは使っていませんでした。

 代わりに接触している b2Bodyの組として MyContactListener::Persistの中で
bodyPairに組を入れて、そのまま vectorコンテナに保存しています。
 これが「接触してるb2Bodyの組リスト」としています。

 これを利用して接触している b2Body同士を調べていたのは GetTotalMassContactedBox関数の中で、
MyContactListener::IsContactedBodiesを使って2つの b2Bodyが接触していたかどうかを
調べていました。



>地面があり、ボックスが2個離れて地面の上に乗っているプログラムがあったとして、
>~という考え方でいいのでしょうか?
 ここで言っている「コンタクトリスト」は「コンタクトリストナー」とは別もので、
地面の b2Body::GetContactListで取得できるリスト(b2ContactEdgeの集合)のことを指しています。
 コンタクトリストを辿るというのは前回のコードでいうと「GetTotalMassContactedBox関数の forループ」
の処理で、あれがまさに「バスケットのコンタクトリストを辿って」います。

 それを踏まえて訂正すると

 1) 地面に接触しているボックスのコンタクトリストを探して、接触している可能性のあるボックスを探す
 2) 接触している可能性のあるボックスがあったら、そのボックスが確実に接しているかどうかを
 コンタクトリストナーのリストで調べる。
 3) 確実に接していることを確認できたら、位置関係を調べて載っているかどうか調べる
 4) 載っていたらクリアにする

となります。



>条件として「接触している」では駄目ですよね。
 そうですね。
 接触しているだけでなく、位置関係も見る必要があります。ただこれはそんなに難しく考えなくても
ボックスの中心位置と幅・高さの情報からそれなりの精度で割り出せます。
 座標が安定していること(ほとんど動いていない)、Y軸的に一方が上にあること、
X軸は上と下のオブジェクトの差が幅より小さいことくらいみておけば大丈夫かと。
(あとはやってみて調整)

Re:Box2Dに関してPart2

Posted: 2010年6月10日(木) 15:47
by るー
>該当する箇所は前回のてんびんでいうと MyContactListener::Persistと
 MyContactListener::IsContactedBodiesです。

 すいません、これはもしかしたら実行ファイルだけ頂いたものですか?
 該当箇所が見当たらないのですが・・。

>コンタクトリストを辿るというのは前回のコードでいうと「GetTotalMassContactedBox関数の forループ」
 の処理で、あれがまさに「バスケットのコンタクトリストを辿って」います。

 こちらはありました。2008バージョンアップした方ですよね。
 かなり記述が複雑で難しそうですね。
 実行はできませんが、見てみます。
 
>接触しているだけでなく、位置関係も見る必要があります。ただこれはそんなに難しく考えなくても
 ボックスの中心位置と幅・高さの情報からそれなりの精度で割り出せます。

 難しく考えてしまうんですよねぇw
とりあえずGetTotalMassContactedBoxの部分を見て辿り方を勉強する他ないようですね。

Re:Box2Dに関してPart2

Posted: 2010年6月10日(木) 16:25
by Justy
> すいません、これはもしかしたら実行ファイルだけ頂いたものですか?
> 該当箇所が見当たらないのですが・・。

 あれ、お持ちじゃないですか?
 GetTotalMassContactedBox関数が入っている main.cppと一緒に入れた(と思う)
MyContactListener.hの方です。

 No:52857でみなかさんが MyContactListener.hで扱っている tuple関連でエラーが出た
ということだったので、みなかさんは持っているかもしれません。
http://www.play21.jp/board/formz.cgi?ac ... 2933#52855

Re:Box2Dに関してPart2

Posted: 2010年6月11日(金) 13:41
by るー
ありがたく頂きます。
とりあえずこのレスでこのスレッドは解決にしたいと思います。
Justyさんには本当に感謝しています。
ありがとうございました。