AS3.0碰撞检测 hitTestObject 与 hitTestPoint

  早先 Flash 中的影片剪辑都有内置 hitTest 方法,这个方法有很多种用途。而现在已
经将它划分为两种方法,这样做更加合理。hitTestObject 方法用于判断两个显示对象间是
否发生了碰撞,而 hitTestPoint 方法用于判断某个点与显示对象间是否发生了碰撞。

碰撞检测两个影片
    使用 hitTestObject 判断两个影片是否碰撞也许是最简单的碰撞检测方法。调用这个
函数作为影片的方法,将另一个影片的引用作为参数传入。注意,虽然我说的是影片,但这
两种方法都是 DisplayObject 类的成员,对于所有继承自显示对象类的子类, MovieClip,
                                                                     如
Bitmap, Video, TextField 等都可以使用。格式如下:
sprite1.hitTestObject(sprite2)
通常于在 if 语句中使用:
if(sprite1.hitTestObject(sprite2)) {
    // 碰撞后的动作
}
    如果两个影片发生了碰撞则返回 true,并执行 if 语句块的内容。一切事物都是把双
刃剑。碰撞检测方法越简单,其精确度就越低;相反,检测的精确度越高,则实现起来就越
复杂。因此,这个最简单的方法,精确度也是最差的。
    那么在碰撞检测中精确度意味着什么呢?不就是判断两个物体有没有冲突吗?我也希
望问题只有这么简单。
    回过头来看问题:知道两个影片的位置,如何判断它们是否碰撞?最简单的判断方法是
这样执行的:拿来一个物体,绕着它画一个矩形。再复制出一个相同的影片,两个之间进行
碰撞检测。最后判断两个矩形之间是否有相交的地方。如果有,则发生碰撞。用矩形包围物
体就是我们所熟知的矩形边界(bounding box)。当我们在 Flash IDE 中点击一个舞台元
件时,就会看到一圈蓝色的轮廓线,如图 9-1 所示。

AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第1张图片

图 9-1 矩形边界
    当然,在 Flash 播放器中,并非先画上一个矩形再进行判断。一切都是根据影片的位
置和大小计算出来的。
    为什么这种方法不精确?因为,一旦两个矩形边界相交,则必然会产生碰撞。下面请见
图 9-2,这几对图形中,哪两个相交了?


AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第2张图片

 

图 9-2 哪一对相交了?
    很明显,只有那两个正方形碰到了,对吧?好的,下面为它们画上矩形边界,再从 Flash
的视角观察一下。结果如图 9-3 所示。
AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第3张图片

图 9-3 并非我们希望的结果
     对于 Flash 来说,每对图形是相交的。大家不相信的话,请看下面这个文档类
ObjectHitTest.as,用到了我们前面创建的 Ball 类,请确认这个类存在于类路径中:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class ObjectHitTest extends Sprite {
   private var ball1:Ball;
   private var ball2:Ball;
   public function ObjectHitTest() {
     init();
   }
   private function init():void {
     ball1 = new Ball();
     addChild(ball1);
     ball1.x = stage.stageWidth / 2;
     ball1.y = stage.stageHeight / 2;
     ball2 = new Ball();
     addChild(ball2);
     ball2.startDrag(true);
     addEventListener(Event.ENTER_FRAME, onEnterFrame);
   }
   private function onEnterFrame(event:Event):void {
     if (ball1.hitTestObject(ball2)) {
      trace("hit");
     }
   }
  }
}
      本例创建了两个 Ball 类的实例,并将其中一个设置为可拖拽的。在每帧中使用
hitTestObject 方法判断两个影片是否发生了碰撞。注意,当我们从上,下,左,右靠近目
标时,结果都是正确的。但是如果倾斜着靠近物体,就有问题了。当然,如果物体都是矩形
的话,不会有什么问题。但物体要是越不规则,那么结果就越不准确。所以,当发生碰撞的
物体不是矩形时,大家就要格外小心。
      下面举一个使用 hitTestObject 判断矩形物体的例子。这里要用到一个崭新的 Box
类,与 Ball 类的非常相似,相信大家理解起来一定没有问题,代码如下:
package {
  import flash.display.Sprite;
  public class Box extends Sprite {
   private var w:Number;
   private var h:Number;
   private var color:uint;
   public var vx:Number = 0;
   public var vy:Number = 0;
   public function Box(width:Number=50,
   height:Number=50,
   color:uint=0xff0000) {
     w = width;
     h = height;
     this.color = color;
     init();
   }
   public function init():void {
     graphics.beginFill(color);
     graphics.drawRect(-w / 2, -h / 2, w, h);
     graphics.endFill();
   }
  }
}
      设计思想是, box 从屏幕上方落到下。 box 落到舞台底部或其它 box 上时,就算放
置好了。下面是代码(文档类 Boxes.as):
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class  Boxes extends Sprite {
   private var  box:Box;
   private var  boxes:Array;
   private var  gravity:Number = 0.2;
   public function Boxes() {
     init();
   }
   private function init():void {
     boxes = new Array();
     createBox();
     addEventListener(Event.ENTER_FRAME, onEnterFrame);
   }
   private function onEnterFrame(event:Event):void {
     box.vy += gravity;
     box.y += box.vy;
     if (box.y + box.height / 2 > stage.stageHeight) {
      box.y = stage.stageHeight - box.height / 2;
      createBox();
     }
     for (var i:uint = 0; i < boxes.length; i++) {
      if (box != boxes[i] && box.hitTestObject(boxes[i])) {
        box.y = boxes[i].y - boxes[i].height / 2 - box.height / 2;
        createBox();
      }
     }
   }
   private function createBox():void {
     box = new Box(Math.random() * 40 + 10, Math.random() * 40 + 10);
     box.x = Math.random() * stage.stageWidth;
     addChild(box);
     boxes.push(box);
   }
  }
}
      在 onEnterFrame 方法中,首先判断 box 位置是否低于舞台底部。如果是则停止,然
后创建下一个 box。在 for 循环中,判断当前 box 是否碰撞到了其它 box。首先要判断,
它不是和自己碰撞的,然后是整个程序的重点 hitTestObject,用来判断当前 box 是否与
其它 box 发生碰撞。如果是则将当前 box 放在那个 box 的上面,随后创建一个新的 box。
如果我们将本例中,Box 类换成 Ball 类,就会出现物体悬浮在空中的情景。

影片与点的碰撞检测
      hitTestPoint 的工作方法有些不同,还带有一个可选参数。这个方法在判断两个影片
的碰撞时并不常用。该方法中有两个 Number 类型的参数,用于定义点。根据影片是否与某
点相交,返回 true 或 false。最基本的形式如下(100,100 代表点的 x,y 坐标):
      sprite.hitTestPoint(100, 100);
      同样,可以在 if 语句中使用它来判断碰撞为 true 时要执行的代码。
      回到问题上:怎样才算碰撞?这时又看到我们的老朋友矩形边界了。Flash 只检查我们
给出的点是否在影片的矩形边界内。用文档类 PointHitTest.as 来看测试一下:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class PointHitTest extends Sprite {
   private var ball:Ball;
   public function PointHitTest() {
     init();
   }
   private function init():void {
     ball = new Ball();
     addChild(ball);
     ball.x = stage.stageWidth / 2;
     ball.y = stage.stageHeight / 2;
     addEventListener(Event.ENTER_FRAME, onEnterFrame);
   }
   private function onEnterFrame(event:Event):void {
     if (ball.hitTestPoint(mouseX, mouseY)) {
      trace("hit");
     }
   }
  }
}
      这次使用鼠标的 x,y 坐标判断与小球碰撞的点。当鼠标接近小球,可能还没有真正碰
触到目标时就产生了碰撞。同样情况下,如果使用矩形方块,就不会有问题了。因此,这个
方法看起来也只对矩形物体有用。但是这里还有一个叫 shapeFlag 的选项。
使用 shapeFlag 执行碰撞检测
    shapeFlag 是 hitTestPoint 方法的第三个参数是可选的。其值是 Boolean 类型的,
因此只有 true 和 false。将 shapeFlag 设置为 true 意味着碰撞检测时判断影片中可见
的图形,而不是矩形边界。注意,shapeFlag 只在能用在检测点与影片的碰撞中。如果是两
个影片的碰撞就不能用这个参数了。
来实验一下,只需要在原有基础上增加一个值,将 hitTestPoint 变为:
if(ball.hitTestPoint(mouseX, mouseY, true))
    (如果不想使用 shapeFlag,可将参数设为 false,与不写参数是完全等价的)用小球
测试一下这个版本的碰撞。 我们发现这回鼠标只有在真正碰撞到影片的图形时才会检测到碰
撞。
     这样我们就实现了非常精确的碰撞检测,但是它并不能应对所有的情况。问题在于使用
的点只有一个,很难看出到底影片的哪些部分碰触到了其它影片。也许大家的第一反是用:
     sprite1.hitTestPoint(sprite2.x, sprite2.y, true)
     但是,如果这样的话,我们只能判断出 sprite2 的注册是否在影片 sprite1 上。这种
用途实在有限。因为 sprite2 的任何部分都有可能碰到 sprite1。实际应用中,最好选择
鼠标或很小的影片作为碰撞点对象,因为它们两个之间也许只有一两个像素的偏差。
     人们曾试图用这种方法检测物体四周上的点。例如,有一个星形的影片,我们可以计算
出星形五个顶点的位置, 然后判断每个顶点与另一个影片是否发生碰撞。但是如果有两个星
形影片, 我们就需要用这个星形的五个顶点与另一个星形的五个顶点进行碰撞判断。如果只
是星形还好说,但要是其它不规则图形,还需要更多的顶点。可以想象这将占用大量的 CPU
资源。使用简单的碰撞检测方法,光是两个星形就要判断多达十次。这就是我们要追求准确
度所负出的代价。

hitTest 总结
     那么在两个不规则的物体间如何检测碰撞?很遗憾,用 hitTest 方法无法实现。
     下面总结一下,hitTest 的基本设置:
■ 对于矩形影片,使用 hitTestObject(displayObject)。
■ 对于非常小的影片,使用 hitTestPoint(x, y, true) 注意将 shapeFlag 设置为 (true)。
■ 对于非常不规则的影片图形,如果不要求非常精确或自定义一些解决方法的话,那么也
可以使用 hitTestPoint(x, y, true)。
     本章内容远没有结束,下面我们还有超越内置方法的碰撞检测法。如果对象是圆形的物
体,那么使用距离碰撞检测方法将是最好的选择。我们会发现原来有很多种图形都属于圆形。

距离碰撞检测
    本节开始,我们就摆脱了内置 hitTest 方法,而是将碰撞检测掌握在自己手里。这就
要用两个物体间的距离来判断碰撞的发生。
    举个现实中的例子,如果你那辆车与我这辆车有 100 米的距离,我们就知道这两辆车
离得足够远,不可能发生碰撞。然而,如果我们的车都有 6 米宽和 12 米长,而我这辆车
的中心点与你那辆车的中心点只有 5 米,那么肯定会有些金属被撞弯,保险单会变长。换
句话讲,除非车子的某些部分被撞掉以外,两辆车不可能并到一起。这就是整个距离碰撞检
测的思想。我们要确认使两个物体分开的最小距离,再看看当前距离,比较两者的大小。如
果当前距离小于最小距离,就知道物体间发生了碰撞。
    hitTestObject 方法在矩形上使用效果最好,但在处理其它图形时就退化了,而我们这
种方法则在处理圆形时效果最好。要是处理的图形与圆形有偏差,则精确度就会有所降低。
但是这里会遇到与 hitTest 中矩形边界相反的问题:明明发生了碰撞,确没有响应,这是
因为它们的中心点还不够近。

简单的距离碰撞检测
    让我们从最理想的状态开始:两个圆形。依然可以使用 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。


AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第4张图片

图 9-4 碰撞的距离
     图中我们看到两个影片发生了碰撞,每个影片都占 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)属性,
可以直接拿来判断。


AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第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);
     测试一下,每次执行小球的半径都不相同,但碰撞的效果依然完美如初。

弹性碰撞
     给大家一个完整的距离碰撞检测的例子,其中包括之前没有讨论的问题,如两个物体碰
撞时的交互以及如何有效地处理多物体间的交互。但我也不想让例子中出现没有学过的内
容。
     我的想法是:放入一个大球,名为 centerBall,在舞台的中心。然后加入多个小球,
给它们随机的大小与速度, 让它们进行基本的运动并在撞墙后反弹。每一帧都在小球与大球
之间进行距离碰撞检测。 如果发生了碰撞,根据它们之间的角度计算出弹性运动的偏移目标
和最小碰撞距离。OK,这么说不是很明白。根本的意思就是,如果小球与 centerBall 发生
碰撞,就把小球弹出去,通过设置 centerBall 外面的目标点来实现,然后让小球向目标点
弹性运动。一旦小球到达目标,就不再产生碰撞,弹性运动结束,继续执行常规的运动。
     运行结果就像小气泡被大气泡反弹回去,如图 9-6 所示。
AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第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 的圆周。
      最后,使用基本的弹性效果,让小球向该点运动(如第八章介绍的)。一旦小球到达目
标点,就不会再发生碰撞,然后向任意方向运动。
      思考一下,我们是怎样用简单的技术制作出非常复杂的运动效果的?

 

多物体碰撞检测方法
    在屏幕上有只有两个物体时,判断它们之间的碰撞是非常简单的。但是如果物体很多时,
我们就需要了解一些碰撞检测的策略,以便不遗漏任何可能发生的碰撞。  当要检测的物体越
来越多时,如果进行有效的判断就显得至关重要。

基本的多物体碰撞检测
      当只有两个物体时,碰撞只可能在 A - B 物体间发生。如果有三个物体,就有三种可
能:A – B, B – C, C – A。如果是四个物体就有六种可能,五个物体就有十种可能。
      如果物体多达 20 个,就需要分别进行判断 190 次。这就意味着在我们的 enterFrame
函数中,需要调用 190 次 hitTest 方法或距离计算。
      如果使用这种方法,那么就会多用出必要判断的两倍!比如说 20 个物体就执行了 380
次 if 语句(20 个影片每个判断 19 次,20 * 19 = 380)。大家现在知道学习本节内容的
重要性了吧。
      看一下问题,审视一下平常的做法。假设我们有六个影片,分别为 sprite0, sprite1,
sprite2, sprite3, sprite4, sprite5。让它们运动并执行反弹,我们想要知道它们之间何
时会发生碰撞。思考一下,依次获得每个影片的引用,然后再执行循环,再去和其它的影片
进行比较。下面是一段伪代码:
numSprites = 6;
for (i = 0; i < numSprites; i++) {
  spriteA = sprites[i];
  for (j = 0; j < numSprites; j++) {
   spriteB = sprites[j];
   if (spriteA.hitTestObject(spriteB)) {
     // 执行代码
   }
  }
}

      六个影片执行了 36 次判断。看上去很合理,对吗?其实,这段代码存在着两大问题。
首先,来看第一次循环,变量 i 和 j 都等于 0。因此 spriteA 所持的引用是 sprite0,
而 spriteB 的引用也是一样。嗨,我们原来是在判断这个影片是否和自己发生碰撞!无语
了。所以要在 hitTest 之前确认一下 spriteA != sprieB,或者可以简单地写成 i != j。
代码就应该是这样:
numSprites = 6;
for (i = 0; i < numSprites; i++) {
  spriteA = sprites[i];
  for (j = 0; j < numSprites; j++) {
   spriteB = sprites[j];
   if (i != j && spriteA.hitTestObject(spriteB)) {
     // do whatever
   }
  }
}
      OK,现在已经排除了六次判断,判断次数降到了 30 次,但还是太多。下面列出每次比
较的过程:
sprite0 与  sprite1, sprite2, sprite3,  sprite4, sprite5 进行比较
sprite1 与  sprite0, sprite2, sprite3,  sprite4, sprite5 进行比较
sprite2 与  sprite0, sprite1, sprite3,  sprite4, sprite5 进行比较
sprite3 与  sprite0, sprite1, sprite2,  sprite4, sprite5 进行比较
sprite4 与  sprite0, sprite1, sprite2,  sprite3, sprite5 进行比较
sprite5 与  sprite0, sprite1, sprite2,  sprite3, sprite4 进行比较
    请看第一次判断: sprite0 与 sprite1 进行比较。
                     用                              再看第二行:sprite1 与 sprite0
进行比较。它俩是一回事,对吧?如果 sprite0 没有与 sprite1 碰撞,那么 sprite1 也
肯定不会与 sprite0 碰撞。或者说,如果一个物体与另一个碰撞,那么另一个也肯定与这
个物体发生碰撞。所以可以排除两次重复判断。 如果删掉重复判断,列表内容应该是这样的:
sprite0 与 sprite1, sprite2, sprite3, sprite4, sprite5 进行比较
sprite1 与 sprite2, sprite3, sprite4, sprite5 进行比较
sprite2 与 sprite3, sprite4, sprite5 进行比较
sprite3 与 sprite4, sprite5 进行比较
sprite4 与 sprite5 进行比较
sprite5 没有可比较的对象!
    我们看到第一轮判断,用 sprite0 与每个影片进行比较。随后,再没有其它影片与
sprite0 进行比较。把 sprite0 放下不管,再用 sprite1 与剩下的影片进行比较。当执行
到最后一个影片 sprite5 时, 所有的影片都已经和它进行过比较了, 因此 sprite5 不需要
再与任何影片进行比较了。结果,比较次数降到了 15 次,现在大家明白我为什么说初始方
案通常执行了实际需要的两倍了吧。
    那么接下来如果写代码呢?仍然需要双重嵌套循环,代码如下:

numSprites = 6;
for (i = 0; i < numSprites - 1; i++) {
  spriteA = sprites[i];
  for (j = i + 1; j < numSprites; j++) {
   spriteB = sprites[j];
   if (spriteA.hitTestObject(spriteB)) {
     // do whatever
   }
  }
}
      请注意,外层循环执次数比影片总数少一次。就像我们在最后的列表中看到的,不需要
让最后一个影片与其它影片比较,因为它已经被所有影片比较过了。
      内层循环的索引以外循环索引加一作为起始。这是因为上一层的内容已经比较过了,而
且不需要和相同的索引进行比较。这样一来执行的效率达到了 100%。
多物体弹性运动
    我们再来看一个小程序。同样也是气泡效果的交互运动,不过这次所有的气泡彼此间都
可以相互反弹。效果如图 9-7 所示。

AS3.0碰撞检测 hitTestObject 与 hitTestPoint_第7张图片

图 9-7 多物体碰撞
      以下代码见文档类 Bubbles2.as:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class Bubbles2 extends Sprite {
   private var balls:Array;
   private var numBalls:Number = 30;
   private var bounce:Number = -0.5;
   private var spring:Number = 0.05;
   private var gravity:Number = 0.1;
   public function Bubbles2() {
     init();
   }
   private function init():void {
     balls = new Array();
     for (var i:uint = 0; i < numBalls; i++) {
      var ball:Ball = new Ball(Math.random() * 30 + 20,
      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 - 1; i++) {
   var ball0:Ball = balls[i];
   for (var j:uint = i + 1; j < numBalls; 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.radius + ball1.radius;
     if (dist < minDist) {
      var angle:Number = Math.atan2(dy, dx);
      var tx:Number = ball0.x + Math.cos(angle) * minDist;
      var ty:Number = ball0.y + Math.sin(angle) * minDist;
      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 < numBalls; 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.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;
     }
   }
  }
}
      本例中的交互动画还需要两点补充说明。这是碰撞后的运动代码:
if(dist < minDist) {
      var angle:Number = Math.atan2(dy, dx);
      var tx:Number = ball0.x + Math.cos(angle) * minDist;
      var ty:Number = ball0.y + Math.sin(angle) * minDist;
      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;
}
    只要 ball0 与 ball1 发生了碰撞就会执行这段代码。基本上与前面例子中的
centerBall 意思相近,只不是用 ball0 代替 centerBall。求出需要它俩的夹角,然后计
算目标点的 x,y 坐标,这就是 ball1 要到达的点,这样做才不会让两个小球碰撞到一起。
接下来,计算出 x,y 轴的加速度 ax,ay。下面要注意了,本例中,不仅 ball1 要向 ball0
弹性运动,而且 ball0 还必需离开 ball1,加速度的方向相反。只需要将 ax 和 ay 加到
ball1 的速度向量中,再从 ball0 的速度向量中将它们减去即可!这样就免去了计算两次
的麻烦。大家也许认为这样做,就相当于把最终的加速度扩大了两倍。是的,您说得没错。
为了弥补这一点,我们将 spring 的值设置得比平时小一些。
    下面说说另一个问题。代码中使用 Math.atan2 计算夹角,然后再用 Math.cos 和
Math.sin 求出目标点:
var angle:Number = Math.atan2(dy, dx);
var tx:Number = ball0.x + Math.cos(angle) * minDist;
var ty:Number = ball0.y + Math.sin(angle) * minDist;
    但是大家不要忘记,正弦是对边与斜边之比,而余弦是邻边与斜边之比。请注意,该角
的对边就是 dy,邻边就是 dx,而斜边就是 dist。所以,我们实际上可以将这三行代码缩
短为两行:
var tx:Number = ball0.x + dx / dist * minDist;
var ty:Number = ball0.y + dy / dist * minDist;
    瞧!只用了两个简单的除法就取代了调用三次三角函数。下面请实验一下这个泡泡球的
例子,试调整 spring,gravity,number 和 ball 的大小,观察运行结果。大家还可以添加
摩擦力或鼠标交互的动作。
碰撞检测的其它方法
    ActionScript 内置的 hitTest 方法与距离碰撞检测方法并不是实现碰撞检测仅有的
方法,但使用它们可以完成大部分的碰撞检测。
     如果要进行更深入的研究,我们会发现聪明的开发者们已经提出了一些非常精巧的碰撞
检测方法。比如,Grant Skinner 提出了一种通过操作多个位图对象,来确定什么时候,哪
些物体之间的像素发生了重叠。大家可以在 www.gskinner.com 找到相关内容。

 

 


本章重要公式
     下面我们来回顾一下本章的两个重要公式。
距离碰撞检测:
// 从 spriteA 和 spriteB 开始
// 如果使用一个空白影片,或影片没有半径(radius)属性
// 可以用宽度与高度除以 2。
var dx:Number = spriteB.x - spriteA.x;
var dy:Number = spriteB.y - spriteA.y;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
if (dist < spriteA.radius + spriteB.radius) {
  // 处理碰撞
}
多物体碰撞检测:
var numObjects:uint = 10;
  for (var i:uint = 0; i < numObjects - 1; i++) {
  // 使用变量 i 提取引用
  var objectA = objects[i];
  for (var j:uint = i+1; j
   //使用变量 j 提取引用
   var objectB = objects[j];
   // 在 objectA 与 objectB 之间进行碰撞检测
  }

你可能感兴趣的:(AS3.0碰撞检测 hitTestObject 与 hitTestPoint)