到目前為止,我們已經學習了物體在其空間的內互動運動。接下來研究一下物體之間的
互動運動。這就需要確定物體間何時發生了碰撞,這就是我們所講的碰撞檢測(Collision
detection 或 Hit testing)。
本章我會盡量將所有需要掌握的相關知識告訴大家。其中包括兩個影片的碰撞,影片與
點之間的碰撞,用距離檢測碰撞以及多物體碰撞檢測方法。首先,來看一下有什麼現成的碰
撞檢測方法。
碰撞檢測方法
碰撞檢測的思想非常非常簡單。我們只要知道兩個物體是否有在同一時間內某個部分處
在了同一位置上。當然,也許物體不只兩個,這就需要知道其中的一個是否和其它的物體發
是了碰撞。
如本節的題目,檢測碰撞的方法有很多:可以是對物體實際像素的檢測(Sprite 或
MovieClip),也就是判斷兩個影片中的圖形是否重疊?對於這種檢測方法,要以影片內圖
形的實際可見像素判斷,還是以影片的矩形邊界來判斷呢?這就涉及到 hitTest 方法中內
置的兩個可選項,用來滿足不同方面的需求。
碰撞也可以根據距離來判斷。獲得兩個物體間的距離,然後問“物體間是否近得足夠發
生碰撞了?”,應用這種方法時需要計算並判斷距離。
每種方法都有它們各自的用途。具體實現會在本章進行講解。至於發生碰撞後該做些什
麼,本章並不涉及。因為,這些內容我們會在第十一章介紹動量守恒時詳細地為大家講解。
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 所示。
圖 9-1 矩形邊界
當然,在 Flash 播放器中,並非先畫上一個矩形再進行判斷。一切都是根據影片的位
置和大小計算出來的。
為什麼這種方法不精確?因為,一旦兩個矩形邊界相交,則必然會產生碰撞。下面請見
圖 9-2,這幾對圖形中,哪兩個相交了?
圖 9-2 哪一對相交了?
很明顯,只有那兩個正方形碰到了,對吧?好的,下面為它們畫上矩形邊界,再從 Flash
的視角觀察一下。結果 9-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);