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 碰撞的距离
图中我们看到两个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 两个不同体积物体的碰撞距离
思路已经有了,距离就是两个小球的半径之和。现在可以删除手工加入的数字了,代码如下(文档类 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 弹性碰撞
下面是代码(可在 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 下午2:44:46 阅读次数:8039