衝突判定 by 距離
本日も続いて衝突判定を試してみます。
今回はhitTestObjectメソッド、hitTestPointメソッドから離れて、距離にもとづく衝突判定を考えてみます。
ここで取り扱うのは2つの円の衝突です。正確に言うと真円(縦横の幅が等しい円)を扱います。
2つの真円が衝突するということはこういうことです。
で、衝突判定をするには、2つの円の半径から衝突するために必要な距離を算出できます。
つまり、
2つの円の距離 < 円Aの半径 + 円Bの半径
の条件を満たすと衝突しているということになります。
この例では、円が真横に並んでるのでわかりやすいけど、実際は縦の位置が違うこともあります。
そんなときに2つの円の距離を算出するには、ピタゴラスの定理を使えばOKです。
// dxは横軸の距離、dyは縦軸の距離 var dist = Math.sqrt(dx * dx + dy * dy);
この仕組みを使ってのサンプルがこちら。
コードはこちら。
package { import flash.display.Sprite; import flash.events.Event; [SWF(width=600, height=600)] public class Bubble2 extends Sprite { private var balls:Array; private var ballCount:Number = 30; private var bounce:Number = -0.5; private var spring:Number = -0.05; private var gravity:Number = 0.1; public function Bubble2() { init(); } private function init():void{ balls = new Array; for(var i:uint; i < ballCount; i++){ var ball:Ball = new Ball(Math.random() * 30 + 20, Math.random() * 0xFFFFFF); ball.x = Math.random() * 600; ball.y = Math.random() * 600; ball.vx = Math.random() * 6 - 3; ball.vy = Math.random() * 6 - 3; addChild(ball); balls.push(ball); } addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(evt:Event):void { // 最後の1回は不要 for(var i:uint = 0; i < ballCount - 1; i++){ var ball0:Ball = balls[i]; // 次のボールからチェック for(var j:uint = i + 1; j < ballCount; j++){ var ball1:Ball = balls[j]; // 距離を算出 var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx * dx + dy * dy); // 衝突判定 var minDist:Number = ball0.radious + ball1.radious; if(dist < minDist){ // 角度 var angle:Number = Math.atan2(dy, dx); // 対象(ball0のオフセットを考慮) var tx:Number = ball0.x + Math.cos(angle) * minDist; var ty:Number = ball0.y + Math.sin(angle) * minDist; // 対象とballの距離にバネ係数を掛ける var ax:Number = (tx - ball1.x) * spring; var ay:Number = (ty - ball1.y) * spring; // 加速(本と+ーを反対にしたけどあってる?) ball0.vx += ax; ball0.vy += ay; ball1.vx -= ax; ball1.vy -= ay; } } } for(i = 0; i < ballCount; i++){ var ball:Ball = balls[i]; move(ball); } } private function move(ball:Ball):void{ ball.vy += gravity; ball.x += ball.vx; ball.y += ball.vy; if(ball.x + ball.radious > stage.stageWidth){ ball.x = stage.stageWidth - ball.radious; ball.vx *= bounce; }else if(ball.x - ball.radious < 0){ ball.x = ball.radious; ball.vx *= bounce; } if(ball.y + ball.radious > stage.stageHeight){ ball.y = stage.stageHeight - ball.radious; ball.vy *= bounce; }else if(ball.y - ball.radious < 0){ ball.y = ball.radious; ball.vy *= bounce; } } } } import flash.display.Sprite; class Ball extends Sprite { public var radious:Number; private var color:uint; public var vx:Number = 0; public var vy:Number = 0; public function Ball(radius:Number = 40, color:uint = 0xff0000) { this.radious = radius; this.color = color; init(); } private function init():void{ graphics.clear(); graphics.beginFill(color); graphics.drawCircle(0, 0, radious); graphics.endFill(); } public function setColor(color:uint):void { this.color = color; init(); } }
コードの要点を抜粋します。
// 最後の1回は不要 for(var i:uint = 0; i < ballCount - 1; i++){ var ball0:Ball = balls[i]; // 次のボールからチェック for(var j:uint = i + 1; j < ballCount; j++){ var ball1:Ball = balls[j]; // 距離を算出 var dx:Number = ball1.x - ball0.x; var dy:Number = ball1.y - ball0.y; var dist:Number = Math.sqrt(dx * dx + dy * dy); // 衝突判定 var minDist:Number = ball0.radious + ball1.radious; if(dist < minDist){ // 角度 var angle:Number = Math.atan2(dy, dx); // 対象(ball0のオフセットを考慮) var tx:Number = ball0.x + Math.cos(angle) * minDist; var ty:Number = ball0.y + Math.sin(angle) * minDist; // 対象とballの距離にバネ係数を掛ける var ax:Number = (tx - ball1.x) * spring; var ay:Number = (ty - ball1.y) * spring; // 加速(本と+ーを反対にしたけどあってる?) ball0.vx += ax; ball0.vy += ay; ball1.vx -= ax; ball1.vy -= ay; } } }
ここでは不要な判定をなくすため、iとjの初期値、ループ条件をいじっています。
2つのボールの衝突を検査するために必要なball0とball1の組み合わせを最低限にしてるかんじです。
あと、このコードではball0を衝突対象(壁のようなイメージ)のオブジェクトとしています。
ball1が対象に衝突するオブジェクト(車のようなイメージ)です。
そのため、衝突したオブジェクトball1に対して速度をマイナス処理している、
言い換えるなら、衝突した車はスピードが落ちる、ようにball1.vx -= axとしています。
あと、
// 角度 var angle:Number = Math.atan2(dy, dx); // 対象(ball0のオフセットを考慮) var tx:Number = ball0.x + Math.cos(angle) * minDist; var ty:Number = ball0.y + Math.sin(angle) * minDist;
の処理は、こんなふうに変換できます。
cosは隣辺/斜辺なので、dx / dist
sinは対辺/斜辺なので、dy / dist
てことで、
// 対象(ball0のオフセットを考慮) var tx:Number = ball0.x + dx / dist * minDist; var ty:Number = ball0.y + dy / dist * minDist;
みたいに書けます。
言われてみたら納得だけど、自分で気づくかと言われたら微妙。
以上、第9章おしまい。