9.3 基于距离的碰撞检测

本节开始,我们就摆脱了内置 hitTest 方法,而是将碰撞检测掌握在自己手里。这就要用两个物体间的距离来判断碰撞的发生。

举个现实中的例子,如果你那辆车与我这辆车有 100 米的距离,我们就知道这两辆车离得足够远,不可能发生碰撞。然而,如果我们的车都有 6 米宽和 12 米长,而我这辆车的中心点与你那辆车的中心点只有 5 米,那么肯定会有些金属被撞弯,保险单会变长。换句话讲,除非车子的某些部分被撞掉以外,两辆车不可能并到一起。这就是整个距离碰撞检测的思想。我们要确认使两个物体分开的最小距离,再看看当前距离,比较两者的大小。如果当前距离小于最小距离,就知道物体间发生了碰撞。

hitTestObject 方法在矩形上使用效果最好,但在处理其它图形时就退化了,而我们这种方法则在处理圆形时效果最好。要是处理的图形与圆形有偏差,则精确度就会有所降低。但是这里会遇到与 hitTest 中矩形边界相反的问题:明明发生了碰撞,却没有响应,这是因为它们的中心点还不够近。

9.3.1 简单的基于距离的碰撞检测

让我们从最理想的状态开始:两个圆形。依然可以使用 Ball 类。(大家现在也许明白了为什么“重用”一词通常与面向对象编程联系在一起了吧。)在应用距离碰撞检测时,圆的注册点应该在中心点上,Ball 类正好符合要求。先要创建两个小球,并设置其中一个为可拖拽的。然后在 enterFrame 函数中进行碰撞检测。到这儿为止,程序与本章的第一个例子相同。只是在判断碰撞时,不是使用if(ball1.hitTestObject(ball2)),而是在 if 语句中判断距离。我们已经学习了计算两个物体间距离的方法,回忆一下第三章的勾股定理。所以,程序开始应该是这样的:

var dx:Number = sprite2.x - sprite1.x;
var dy:Number = sprite2.y - sprite1.y;
var dist:Number = Math.sqrt(dx * dx + dy * dy); 

OK,现在距离已经有了,如何进行判断呢?请看图 9-4。

图9-4

图 9-4 碰撞的距离

图中我们看到两个Sprite发生了碰撞,每个Sprite都占60 像素宽,那么每个半径就是 30。因此,在它们相互碰撞时,实际相差 60 个像素。啊哈!这就是答案。对于两个大小相同的圆来说,如果距离小于直径,就会产生碰撞。本例代码 (Distance.as) 与 ObjectHisTest.as非常相似,设置 onEnterFrame 方法为:

private function onEnterFrame(event:Event):void
{
    var dx:Number = ball2.x - ball1.x;
    var dy:Number = ball2.y - ball1.y; 
    var dist:Number = Math.sqrt(dx * dx + dy * dy);
    // 默认 ball 的直径为 80 (半径为 40)
    if (dist < 80) {
        trace("hit");
    }
}

测试后,我们发现这回碰撞的结果与接近小球的角度无关。在没有接触到目标球时不会产生碰撞。但是在代码中使用数值表示距离显然不太合适,因为这样的话每次改变 ball 的大小都要重新修改代码。况且,如果两个小球的大小不同怎么办?我们需要将这个方法抽象成可以适应任何情况的公式。如图 9-5 所示。

两个大小不同的 ball,相互碰撞。左边的小球 60 像素,右边的 40 像素。我们可以用程序检察它们的 width 属性。第一个 ball 的半径为30, 另一个半径为20。

所以,它们碰撞时的距离实际应为 50。在 Ball 类里面,已经设置了半径(radius)属性,可以直接拿来判断。

图9-5

图 9-5 两个不同体积物体的碰撞距离

思路已经有了,距离就是两个小球的半径之和。现在可以删除手工加入的数字了,代码如下(文档类 Distance2.as):

private function onEnterFrame(event:Event):void
{
    var dx:Number = ball2.x - ball1.x; 
    var dy:Number = ball2.y - ball1.y; 
    var dist:Number = Math.sqrt(dx * dx + dy * dy);
    if (dist < ball1.radius + ball2.radius) { 
        trace("hit");
    }
}

实验一下,设置小球的大小(将 radius 传入 Ball 的第一个参数中),观察执行结果。在前面例子中,我是这样写的:

ball1 = new Ball(Math.random() * 100); 

测试一下,每次执行小球的半径都不相同,但碰撞的效果依然完美如初。

9.3.2 基于弹性的碰撞

给大家一个完整的距离碰撞检测的例子,其中包括之前没有讨论的问题,如两个物体碰撞时的交互以及如何有效地处理多物体间的交互。但我也不想让例子中出现没有学过的内容。

我的想法是:放入一个大球,名为 centerBall,在舞台的中心。然后加入多个小球,给它们随机的大小与速度,让它们进行基本的运动并在撞墙后反弹。每一帧都在小球与大球之间进行距离碰撞检测。如果发生了碰撞,根据它们之间的角度计算出弹性运动的偏移目标和最小碰撞距离。OK,这么说不是很明白。根本的意思就是,如果小球与 centerBall 发生碰撞,就把小球弹出去,通过设置 centerBall 外面的目标点来实现,然后让小球向目标点弹性运动。一旦小球到达目标,就不再产生碰撞,弹性运动结束,继续执行常规的运动。

运行结果就像小气泡被大气泡反弹回去,如图 9-6 所示。

图9-6

图 9-6 弹性碰撞

下面是代码(可在 Bubbles.as 中找到):

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class Bubbles extends Sprite
	{
		private var balls:Array;
		private var numBalls:Number = 10;
		private var centerBall:Ball;
		private var bounce:Number = -1;
		private var spring:Number = 0.2;
		
		public function Bubbles()
		{
			init();
		}
		
		private function init():void
		{
			balls = new Array();
			centerBall = new Ball(100, 0xcccccc);
			addChild(centerBall);
			centerBall.x = stage.stageWidth / 2;
			centerBall.y = stage.stageHeight / 2;
			
			for(var i:uint = 0; i < numBalls; i++)
			{
				var ball:Ball = new Ball(Math.random() * 40 + 5, Math.random() * 0xffffff);
				ball.x = Math.random() * stage.stageWidth;
				ball.y = Math.random() * stage.stageHeight;
				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(event:Event):void
		{
			for(var i:uint = 0; i < numBalls; i++)
			{
				var ball:Ball = balls[i];
				move(ball);
				var dx:Number = ball.x - centerBall.x;
				var dy:Number = ball.y - centerBall.y;
				var dist:Number = Math.sqrt(dx * dx + dy * dy);
				var minDist:Number = ball.radius + centerBall.radius;
				if(dist < minDist)
				{
					var angle:Number = Math.atan2(dy, dx);
					var tx:Number = centerBall.x + Math.cos(angle) * minDist;
					var ty:Number = centerBall.y + Math.sin(angle) * minDist;
					ball.vx += (tx - ball.x) * spring;
					ball.vy += (ty - ball.y) * spring;
				}
			}
		}
		
		private function move(ball:Ball):void
		{
			ball.x += ball.vx;
			ball.y += ball.vy;
			if(ball.x + ball.radius > stage.stageWidth)
			{
				ball.x = stage.stageWidth - ball.radius;
				ball.vx *= bounce;
			}
			else if(ball.x - ball.radius < 0)
			{
				ball.x = ball.radius;
				ball.vx *= bounce;
			}
			if(ball.y + ball.radius > stage.stageHeight)
			{
				ball.y = stage.stageHeight - ball.radius;
				ball.vy *= bounce;
			}
			else if(ball.y - ball.radius < 0)
			{
				ball.y = ball.radius;
				ball.vy *= bounce;
			}
		}
	}
}

是的,整个代码非常多,但是多数内容前面章节中都介绍过。让我们快速浏览一下。

从 init 函数开始,创建一个 centerBall ,然后循环创建小球,并让小球运动,并给它们随机的大小,位置,颜色和速度。 enterFrame 函数循环得到每个小球的引用。为了分散功能函数,我们将运动代码放到另一个函数中,然后进行调用,参数是小球的引用。这个函数的内容也是我们再熟悉不过的了,只是基本的运动和反弹。下面,求出小球与 centerBall 的距离,然后求出碰撞检测的最小距离。如果发生了碰撞,就要计算出小球与 centerBall 的夹角,再加上最小距离计算出目标点的 x,y。这个目标点就是 centerBall 的圆周。

最后,使用基本的弹性效果,让小球向该点运动(如第八章介绍的)。一旦小球到达目标点,就不会再发生碰撞,然后向任意方向运动。

思考一下,我们是怎样用简单的技术制作出非常复杂的运动效果的?

文件下载(已下载 2705 次)

发布时间:2011-6-27 14:44:46  阅读次数:6361

评论

由于网络审查方面的原因,本网站即日起关闭留言功能,若有什么问题可致信站长邮箱:fjphysics@qq.com。