Murayama blog.

プログラミングと、その次の話

衝突判定 hitTestObject hitTestPoint


AS3.0アニメ本も9章に入りました。やっと半分くらいです。


9章のお題は衝突判定。
衝突、ぶつかったかどうかを判定することですね。
これが理解できれば、グラディウス的なシューティングゲームが実装できるかもしれません。
あるいはスターソルジャーか。
メガドライブサンダーフォース3は隠れた名作でしたね。


ゲームの話は置いといて、衝突の話に入ります。
この章を読むまで知らなかったんですが、
表示オブジェクトの基底クラス(語弊があるかも)であるDisplayObjectクラスでは、
あらかじめ衝突を判定するhitTestObjectメソッドと、hitTestPointメソッドが用意されています。


hitTestObjectメソッドは引数にDisplayObject型のオブジェクトをとります。
つまり、引数に指定したDisplayObjectオブジェクトとの衝突判定を返します。
一方のhitTestPointメソッドは引数に位置(x, y)をとります。
引数の位置と重なったかどうかを判定します。


なんだか便利そうなメソッドもあるし、衝突判定簡単じゃない、と思ったんですけど、
そう簡単にはいかないようです。
まずは、以下のサンプルをご覧下さい。


このサンプルでは左上のボールをドラッグすることで、
右下のボールと衝突させることができます。
衝突するとボールが点滅します。そんなプログラムです。


では実際に衝突させてみます。
まずは真横から。

うん、おっけー。


じゃー次、左上から、、すると、

衝突していないのにボールが点滅してしまいます。


この理由は、hitTestObjectメソッドはオブジェクトの見た目ではなくて、
実際はこんなふうに境界線をもっていて、その境界線を衝突判定対象としているためです。


結局、hitTestObjectメソッドは、円などの衝突判定を上手く判定することはできないということです。
逆に矩形(くけい=長方形)の衝突であれば問題なく判定できます。


衝突判定するところはこんなかんじ。

			// オブジェクトの境界は目に見えるものと違う
			if(ball1.hitTestObject(ball2)){
				ball2.setColor(0xFFFFFF * Math.random());
			}


サンプルプログラムはこんなかんじ。

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	public class ObjectHitTest extends Sprite
	{
		private var ball1:Ball;
		private var ball2:Ball;
		
		public function ObjectHitTest()
		{
			init();
		}
		
		private function init():void
		{
			ball1 = new Ball;
			ball1.x = 100;
			ball1.y = 100
			ball2 = new Ball
			ball2.x = 200;
			ball2.y = 200;
			addChild(ball1);
			addChild(ball2);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			ball1.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown_ball1);
		}

		private function onEnterFrame(evt:Event):void
		{
			// オブジェクトの境界は目に見えるものと違う
			if(ball1.hitTestObject(ball2)){
				ball2.setColor(0xFFFFFF * Math.random());
			}
		}

		Private function onMouseDown_ball1(evt:MouseEvent):void
		{
			ball1.startDrag();
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp_stage);
		}

		private function onMouseUp_stage(evt:MouseEvent):void
		{
			ball1.stopDrag()
			stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp_stage);	
		}
	}
}

import flash.display.Sprite;

class Ball extends Sprite
{
	private 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();
	}
}


あと、もう一つのhitTestPointメソッドについても確認しておく。
例えば、マウスカーソル位置との衝突を判定する場合は次のようになる。

			if(ball.hitTestPoint(mouseX, mouseY)){
				trace("hit");
			}


で、大体想像がつくように、hitTestPointメソッドもデフォルトでは、
hitTestObjectメソッドと同じ問題に遭遇してしまう。
つまり、見た目の衝突ではなく目に見えない境界線の衝突で判定してしまう。


ただ、hitTestPointメソッドはもう少し優秀で、第3引数にtrueを指定すると、
目に見えない境界線による衝突判定ではなく、見た目の衝突判定に切り替えることができる。

			if(ball.hitTestPoint(mouseX, mouseY, true)){
				trace("hit");
			}


じゃーhitTestPointメソッドで第3引数をtrueにすればすべて解決!、、
とはいかない。
実際、必要になるのは、位置との衝突判定ではなく、異なる表示オブジェクトとの衝突判定の場合はほとんどである。


まとめると、
hitTestObjectメソッドは矩形オブジェクトの衝突判定には使える。
hitTestPointメソッドは特定の位置との衝突判定には使える。


で、先に次回予告をしておくと、円オブジェクトの衝突には、オブジェクト間の距離を計算するといった処理が必要になる。


つづく。