.所有範例檔下載
前言
經常使用hitTest()的人應該都知道,hitTest有著一些限制,例如矩形範圍限制,即使hitTest(x,y,true)也只是解決一半的問題;還有frame by frame的限制,例如當位移量大於被偵測物時,那碰撞事件就會被忽略了。
總之這個章節是讓我們練習不用hitTest,自己手工打造碰撞偵測的方法。
第一節 點對圓的碰撞
範例檔:collision.fla

點對圓的碰撞是相當簡單的,因為你只要知道點跟圓的距離,及圓本身的半徑就能測到兩者是否碰撞了。
只要 點跟圓的距離 小於 圓的半徑 那麼就表示有碰到,反之則否。
這個範例中,如上圖的描述,我們放了兩個點,一個在圓內,一個在圓外,各別計算他們離圓心的距離,再跟圓的半徑比大小,就能知道是否碰撞了。
//碰撞函式 function collision(pointMc, cycleMc) { var dx = cycleMc.x-pointMc.x; var dy = cycleMc.y-pointMc.y; var distance = Math.sqrt(dx*dx+dy*dy); trace(pointMc.name+"離圓心的距離為:"+distance); if (distance>cyc.radius) { trace(pointMc.name+"沒碰到圓。"); } else { trace(pointMc.name+"碰到圓了!"); } }
第二節 圓對圓的碰撞
範例檔:collision2.fla

這節除了判斷碰撞外,還加上了圓的運動狀態。
圓跟圓的碰撞原理也是相當容易的。
同樣先取得兩圓心間的距離,再來只要比對兩圓半徑的和。
當 兩圓心間距離 小於 兩圓半徑的和 則表示有碰到。
//碰撞函式 function collision(obj1, obj2) { var dx = obj1.x-obj2.x; var dy = obj1.y-obj2.y; var distance = Math.sqrt(dx*dx+dy*dy); if (distance>(obj1.radius+obj2.radius)) { trace("沒事"); //cFlag = false; } else { trace("啊!!!!!!!!!!!撞到了!"); //cFlag = true; } }
第三節 圓對圓-不受frame限制
範例檔:collision3.fla
我所謂的frame限制,是指在flash中的移動,是一個個frame來動的,例如你設的位移量可能是10,那麼在flash中,就是一次10px地跳躍式前進。但真實世界中可不是這樣的,真實世界中的移動是連續性的,而不是一秒跳多少公分這樣算的。但這種限制是無可避免的,因為flash就是這樣一套建立於frame基礎上的軟體。我們只能儘量地擺脫並模擬真實世界,但其最根源還是frame by frame的。
前兩節都有受限於這樣的frame限制,下面這個範例就是要來嘗試擺脫它了
它大概的原理是,在畫面上的圓要移到下一個位置時,事先算好是否會碰撞
也因此程式會比上述的麻煩些。它把時間因素加進去算,然後用一元二次方程式的公式去解。
//預先測碰撞 function collision(obj1, obj2) { //設解答變數,若無解則為1 collisionTime = 1; //設定位移變數 var xmov1 = obj1.xmov; var ymov1 = obj1.ymov; var xmov2 = obj2.xmov; var ymov2 = obj2.ymov; //設舊的位置變數 var xold1 = obj1.xpos; var yold1 = obj1.ypos; var xold2 = obj2.xpos; var yold2 = obj2.ypos; //定義公式所需之各小變數 var R = obj1.r+obj2.r; var a = -2*xmov1*xmov2+xmov1*xmov1+xmov2*xmov2; var b =-2*xold1*xmov2-2*xold2*xmov1+2*xold1*xmov1+2*xold2*xmov2; var c =-2*xold1*xold2+xold1*xold1+xold2*xold2; var d =-2*ymov1*ymov2+ymov1*ymov1+ymov2*ymov2; var e =-2*yold1*ymov2-2*yold2*ymov1+2*yold1*ymov1+2*yold2*ymov2; var f =-2*yold1*yold2+yold1*yold1+yold2*yold2; var g = a+d; var h = b+e; var k = c+f-R*R; //計算公式 var sqRoot = Math.sqrt(h*h-4*g*k); var t1 = (-h+sqRoot)/(2*g); var t2 = (-h-sqRoot)/(2*g); //若第一個根小於1且大於0 if (t1>0 && t1< =1) { collisionTime = t1; cycleCollided = true; } //若第二根小於1且大於0 if (t2>0 && t2< =1) { //且第二根小於第一根或是無第一根,則以第二根為解 if (collisionTime == null || t2<=t1) { collisionTime = t2; cycleCollided = true; } } if (cycleCollided) { //發出碰撞訊號 trace("碰到了!!"); } }二元二次就是像: aX平方+bX+c=0這種的 而解法是: 2a分之 -b 加減 根號 b平方-4ac (因為不會打數學符號,其實看圖比較清楚)




_root.createEmptyMovieClip("clip", 1); clip.lineStyle(0, 0x000000, 100); line1 = {}; line1.m = 1; line1.b = 100; function findY(line, x) { var y = line.m*x+line.b; return y; } function drawLine(line) { //決定一個 x var x = 200; //求得y var y = findY(line, x); //線的起點 clip.moveTo(x, y); //決定另一個x var x = 100; //求另一個y var y = findY(line, x); //畫出線 clip.lineTo(x, y); } drawLine(line1);可畫出來 相當容易的一個練習 第五節 線與線的交叉 範例檔:collision5_drawLine2.fla 當兩條的斜率不同,則此兩線將會交叉 這一節就是要做這個練習,先在場景中畫出兩斜率不同的線, 並求出其交叉點的位置 畫線的部份就如同上一節 求交叉點的部份原理如下 已知:y=m1*x+b1 y=m2*x+b2 所以:m1*x+b1=m2*x+b2 m1*x-m2*x=b2-b1 x=(b2-b1)/(m1-m2) 改寫成as code就是:
function findIntersection(line_a, line_b) { var x = (line_b.b-line_a.b)/(line_a.m-line_b.m); var y = line_a.m*x+line_a.b; dot._x = x; dot._y = y; }第六節 判斷碰撞是否發生 範例檔:collision6_drawLine3.fla 這一節仍是上一節的延伸, 上一次知道當斜率不同時,此兩線將會交叉,但不能得知這個交叉是否發生了。 一切也是延用上一節的做法,只是最後再判斷一下交叉點有沒有在線上。 而這個判斷法也只是用x、y值比比大小而已,所以不會太複雜。 所謂 兩線將會交叉,但目前未發生,就像


//判斷目前是否發生交叉 function collision(lineA, lineB) { if ((dot._x>=lineA.x1 and dot._x< =lineA.x2) || (dot._x<=lineA.x1 and dot._x>=lineA.x2) || (dot._y>=lineA.y1 and dot._y< =lineA.y2) || (dot._y<=lineA.y1 and dot._y>=lineA.y2)) { lineA.collision = true; } if ((dot._x>=lineB.x1 and dot._x< =lineB.x2) || (dot._x<=lineB.x1 and dot._x>=lineB.x2) || (dot._y>=lineB.y1 and dot._y< =lineB.y2) || (dot._y<=lineB.y1 and dot._y>=lineB.y2)) { lineB.collision = true; } if (lineA.collision and lineB.collision) { trace("目前發生碰撞了!!!!"); } else { trace("目前沒事。"); } }第七節 球與線的碰撞 範例檔:collision7_cycleLine.fla 這節的主旨是偵測移動中的球與線之間的碰撞,書中提出四個步驟: step1.找出球的運動路徑與線的交叉點 step2.運用三角函數,找出球碰到線時的位置 step3.同上,找出球跟線碰到的接觸點 step4.計算從目前位置到碰觸位置所需的frame數(時間),若大於0小於等於1,則表示碰撞發生。 配合下圖解釋:

function getFrames(tempLine, point) { //========Step 1============ //球路徑斜率 var slope2 = point.ymov/point.xmov; //預防斜率無限大(垂直線)或無限小(水平線)狀況 if (slope2 == Number.POSITIVE_INFINITY) { var slope2 = 1000000; } else if (slope2 == Number.NEGATIVE_INFINITY) { var slope2 = -1000000; } //球路徑的y軸截點b2 var b2 = point.y-slope2*point.x; //求出球路徑與線的交叉點 var x = (b2-tempLine.b)/(tempLine.slope-slope2); var y = tempLine.slope*x+tempLine.b; //===========Step 2=============== //球路徑的角度 var theta = Math.atan2(point.ymov, point.xmov); //球路徑與線之夾角 var gamma = theta-tempLine.angle; //碰觸時,"球圓心"與"球路徑及線交叉點"之距離 var sinGamma = Math.sin(gamma); var r = point.radius/sinGamma; //求出碰觸時圓心座標 x, y var x = x-r*Math.cos(theta); var y = y-r*Math.sin(theta); //==================Step 4================ //求出碰觸所需之frame數 var dis =Math.sqrt((x-point.x)*(x-point.x)+(y-point.y)*(y-point.y)); var vel =Math.sqrt(point.xmov*point.xmov+point.ymov*point.ymov); var frames = dis/vel; //======================Step 3================= //找出碰觸點 //求tempLine.slope的垂直線=slope2a var slope2a = -1/tempLine.slope; var b2a = y-slope2a*x; //碰觸點座標xa,ya var xa = (tempLine.b-b2a)/(slope2a-tempLine.slope); var ya = slope2a*xa+b2a; //檢查碰觸點是否在線段上 if ((xa>tempLine.x1 && xa< templine .x2) ||(xa< tempLine.x1 && xa>tempLine.x2) ||((ya>tempLine.y1 && ya<templine .y2) ||(ya<tempLine.y1 && ya>tempLine.y2))) { //是 } else { //否 //讓frame數到1000 var frames = 1000; } return frames; } function getTempPositions() { ball.tempx = ball.x+ball.xmov; ball.tempy = ball.y+ball.ymov; } function bankCollisionDetect() { for (var i = 0; i< linearray .length; ++i) { var frame = getFrames(lineArray[i], ball); if (frame<=1 && frame>0) { //碰到了 trace("撞到了!!"); } } }Step1就像之前介紹過的求兩線交叉點的方法。 Step2之所以會寫成這樣,可能就要看一下圖了

var theta = Math.atan2(point.ymov, point.xmov); //先求得theta角 var gamma = theta-tempLine.angle; //進而求得gamma角 var sinGamma = Math.sin(gamma); var r = point.radius/sinGamma; //利用gamma角及圓半徑,取得r之長度 var x = x-r*Math.cos(theta); //利用r之長度,及之前找到的球路徑與線交叉點座標,來取得球碰撞時的圓心座標 var y = y-r*Math.sin(theta);Step4 很簡單,只要知道目前離碰撞點的距離,除以每frame位移距離,就能得知目前到碰撞所需的frame數 Step3 要先知道,球跟線碰撞時,碰撞點跟圓心連成的那條半徑,必定是跟碰撞線形成垂直關係的。 所以我們把那條半徑算出來,再用之前介紹的線與線碰撞偵測,就能方便算出碰撞點的座標了。 最後再檢查這個點有沒有在碰撞線上,就大功告成了。 第八節 點跟矩形的碰撞偵測 範例檔:collision8_pointRectangle.fla

function pointRectangleDetection(point, rectangle) { //點的位置 var x = point.x; var y = point.y; //矩形的左右邊界 var x1 = rectangle.x; var x2 = x1+rectangle.width; //上下邊界 var y1 = rectangle.y; var y2 = y1+rectangle.height; //以比大小方式,判斷點是否在矩形? if (x>x1 && x< x2 && y>y1 && y< y2) { trace("碰到了!!"); } }有一點值得討論一下 書中範例在最後執行的部份是這樣
EnterFrame = function() { getTempPositions(); pointRectangleDetection(point1, rectangle1); render(); };所以當第一次發出碰撞訊號時,黑點其實已經畫到碰撞的下一格位置了。 要修正這點,我想可能要在render()之前做個判斷,或是乾脆把碰撞偵測跟render寫成一個function吧 不過這只是個範例,所以也不是那麼重要吧… 第九節 矩形跟矩形的碰撞偵測 範例檔:collision9_2rectangle.fla 矩形之所以簡單,是因為都是直角,也就是能夠直接用x y 值比一比就行了 (去年做的橫向捲軸demo,就是用這種方式) 這節也是用類似上一節的方式。比較兩矩形邊界的X Y值,來判斷是否碰撞。 後續討論 此章節主要內容大概就到這邊了,後面加了一些討論,順便記在這裏: 關於多邊形的碰撞呢?例如一個八角形跟一個球的碰撞該如何做? 用上述討論的球與線的碰撞,做八次就可以了 同理我們也可以做六角形、五角形、星形等複雜圖形的碰撞。 ch5總算k完了,接著ch6才是碰撞反應,趁這幾天有點空,一次把這兩章k完才算完整。
沒有留言:
張貼留言