動的当たり判定

そんな用語はありません。私が勝手に作りました。

矩形同士の当たり判定というのはよく扱うわけで方法が載っているサイトはたくさん存在します。そんなに難しい話しではありませんが一応解説。
いきなり2次元で考えるとちょっと難しいので1次元で考えて見ましょう。当たっている状態を考えるのもちょっと面倒なので、当たっていない状態を考えてみましょう。棒1の右の点をright1、左の点をleft1で棒2に関しても同様とする。当たっていない条件は、

( right1 < left2 ) || ( right2 < left1 )

となるのはちょっと考えればわかるだろう。んで、欲しいのはこの逆なので

!( ( right1 < left2 ) || ( right2 < left1 ) )

となる。さらに、ド・モルガンの法則により、

!( right1 < left2 ) && !( right2 < left1 )

となり、不等号が逆になるので

( right1 => left2 ) && ( right2 => left1 )

となる。これで1次元はできた。2次元に拡張するにはどうするか?今度はY座標も考えればよいだけの話しで、両方満たしていればよい。片方だけしか満たしていないと成り立たないのはちょっと考えればわかると思う。ので、

( right1 => left2 ) && ( right2 => left1 ) && ( top1 => bottom2 ) && ( top2 => bottom1 )

となる。これで完成。

というわけで、ただ単に矩形同士の当たり判定を考えるのはそう難しい話ではない。では、動的とはどういうことか?アクションゲームを作るときに重要になったりするのだが、キャラクターがある速度で動いていて壁に当たったとする。プログラム内部では離散時間を採用しているので都合よく壁にピッタリのところでは止まってくれないのが普通である。少し壁に入ってしまう。で、これを修正する必要がある。壁に入ってしまったかどうかは普通の当たり判定でOKだが、そこから外に出すにはどちら側から入ってきたかを考える必要がある。さて、これをどうすればよいか?
具体的に考えてみよう。矩形1が矩形2の左下から入ってきて重なった。実際には重なって欲しくないので適度に修正したい。矩形2の下の辺にあたったなら下に跳ね返らせ、左の辺に当たったのなら左に跳ね返したいので、どちらの辺に当たったかを考えたい。
簡略のため、重なり方は矩形2の左下の方に矩形1が重なっているとする。まずは(top1 - bottom2, right1 - left2 )なるベクトルを考えてみよう。もしこれと速度ベクトルの方向が一致していたらどうか?実際に図に描いてみないとわかりにくいと思うが、これは下の辺でも左の辺でもなくちょうど角にぶつかった事になる。のでこのベクトルと速度ベクトルを比較してみればよさそうである。もし下からあたっていたらどうなるか?先程のベクトルより速度ベクトルの方が右にすこし回っているのがわかると思う。逆に左から当たった場合は逆に左に回っているのが判るはずである。つまり、右回りか左回りかで判別が可能である。右か左かはベクトルの外積で判別できる。これで出来上がり。あとは速度ベクトルの場合分け(左下、右下、左上、右上)で考えればできる。
では次に、矩形2も動いていたらどうなるか?それは簡単で、相対速度で考えればよい。そうすると矩形2が止まっているとして扱える。
というわけで実際にプログラムに書き起こしてみると・・・。

//矩形1が矩形2のどちらからぶつかったかを計算。
//0:そもそも当たっていない
//1:矩形2に対して左から
//2:矩形2に対して上から
//3:矩形2に対して右から
//4:矩形2に対して下から
//5:な・・・なんだって〜!!??

int DynamicJudge( RECT& rect1, RECT& rect2, LONG vx1,LONG vy1, LONG vx2 = 0, LONG vy2 = 0 )
{
	const bool d1 = ( rect1.right => rect2.left ) && ( rect2.right => rect1.left );
	const bool d2 = ( rect1.top => rect2.bottom ) && ( rect2.top => rect1.bottom );
	if( !d1 || !d2 ){
		//当たってないよ。
		return 0;
	}
	
	//相対速度算出
	const LONG rvx = vx1 - vx2;
	const LONG rvy = vy1 - vy2;
	
	//動きが1次元的なら簡単。
	if( rvx == 0.0f ){
		if( rvy > 0.0f ){ return 4; }
		if( rvy < 0.0f ){ return 2; }
	}
	if( rvy == 0.0f ){
		if( rvx > 0.0f ){ return 1; }
		if( rvx < 0.0f ){ return 3; }
	}

	//動きが2次元的だとちと難しい。
	if( ( rvx > 0.0f ) && ( rvy > 0.0f ) ){
		//矩形1が左下から来た
		const LONG rx = rect1.right - rect2.left;
		const LONG ry = rect1.top - rect2.bottom;
		const LONG z = rx * rvy - ry * rvx;
		if( z > 0 ){ return 4; }
		return 1;
	}
	if( ( rvx > 0.0f ) && ( rvy < 0.0f ) ){
		//矩形1が左上から来た
		const LONG rx = rect1.right - rect2.left;
		const LONG ry = rect1.bottom - rect2.top;
		const LONG z = rx * rvy - ry * rvx;
		if( z > 0 ){ return 1; }
		return 2;
	}
	if( ( rvx < 0.0f ) && ( rvy > 0.0f ) ){
		//矩形1が右下から来た
		const LONG rx = rect1.left - rect2.right;
		const LONG ry = rect1.top - rect2.bottom;
		const LONG z = rx * rvy - ry * rvx;
		if( z > 0 ){ return 3; }
		return 4;
	}
	if( ( rvx < 0.0f ) && ( rvy < 0.0f ) ){
		//矩形1が右上から来た
		const LONG rx = rect1.left - rect2.right;
		const LONG ry = rect1.bottom - rect2.top;
		const LONG z = rx * rvy - ry * rvx;
		if( z > 0 ){ return 2; }
		return 3;
	}

	//相対速度0でどうやって当たるっちゅうねん。
	return 5;
}