2003/12/12

碰撞反應

同樣是FLASH MX Game Design Demystified這本書的筆記,今天終於搞定第六章 碰撞反應,這裏牽扯到一些物理知識,有興趣的同好可以參考看看,並歡迎共同討論。

Chapter 6. Collision Reactions
.所有範例檔下載
*這裏分的節次,並不是書中的節次。只是方便我自己整理書中重點及個人心得而分的。(所以說是筆記嘛)

第一節 點跟矩形
範例檔:collisionReaction_1.fla

這節算是我自己加的,書中並沒有此節
為了循序漸進,所以我先用上一章第八節來改。在ch5筆記的第八節中,其碰撞偵測非常簡單
//以比大小方式,判斷點是否在矩形裏
if (x>x1 and x< x2 and y>y1 and y< y2 ) {
trace("碰到了!!");
}

而我們要做碰撞反應的話,最簡單的方式就是:
當 點碰到上邊界時 --> y位移量 乘以 -1 (就是往反方向彈的意思)
當 點碰到下邊界時 --> y位移量 乘以 -1
當 點碰到左邊界時 --> x位移量 乘以 -1
當 點碰到右邊界時 --> x位移量 乘以 -1
所以上述的函式我們必需拆開變四部份才行:
if (x < x1 ) {
point.xmov *= -1;
point.tempx = x1;
}
if (x>x2) {
point.xmov *= -1;
point.tempx = x2;
}
//碰到上下邊界
if (y < y1 ) {
point.ymov *= -1;
point.tempy = y1;
}
if (y>y2) {
point.ymov *= -1;
point.tempy = y2;
}

原則大概就這樣吧


第二節 pong試做
範例檔:collisionReaction_2.fla

pong是一套經典且古老的電玩(關於它的歷史可能要另外寫一篇),應該很多人看過,如下圖

左右各一個上下移動的板子,碰到球就反彈,利用之前學過的技巧,這種遊戲是能夠很簡單地做出來的。
這部份我是以延用上一節的檔案來改的,跟原書中的寫法不太一樣,不過原理大致相同。

1.球跟周圍場景的互動跟上一節一樣,只是當碰到左邊界時,右方得一分,碰到右邊界時,左方得一分,並讓球回到場地中央重新開始。
2.球跟板子的互動,其實跟場景的互動原理類似,只是場景是把球框在裏面,而板子是把球彈在外面,因此將碰撞偵測的寫法修改一下:當發現球跟板子重疊時,就是碰到了。且碰到後,馬上將球的x位移量* -1。
3.玩家是操縱左邊板子,左板主要是跟隨滑鼠的y位置而定,另外加上"別讓板子超出場景"的預防措施
4.右板是代表電腦,電腦就直接跟隨球的y值,以範例中的ai設定,電腦幾乎是不會輸球的。不過沒差,這章不是要講ai,所以不用計較太多。

第三節 自由落體小球collisionReaction_3.fla

這節只是要做一個自由落體小球,落到地面上,又彈起,又落下,且速度一直衰減到靜止狀態
其實這個範例在前幾章就提過了(如果你也有看這本書的話就知)
不過這裏再複習一下而已。
我想這個練習的重點在於,位移量(速度)的動態變化吧

先做好球跟地板,再寫好球跟地板的碰撞,因為只有y值的變化,所以很簡單。
之後只要在ymov時,隨著時間累加一個g值(重力),
並在碰到地時,多乘一個衰減值。(否則球彈起高度跟落下高度太相近,感覺像在太空中)
如此而已。

由於這個範例在前面的章節就教過了,所以我就直接自己寫了,因此會跟書中內容不太一樣,不過原理是相同的。


第四節 球與線的碰撞反應
範例檔:collisionReaction_4.fla

前幾節討論的,都是水平、垂直的線條,我們只要比比xy值的大小就行了,但並不能應用於斜線上,這節的重點就是這個。
如果我的理解沒錯,我想原則在於:
先把球的前進向量拆解成兩個相互垂直的向量,其中一個向量與碰撞目標線平行,另一條則與它垂直。當發生碰撞後,垂直那條將會反轉。
圖解會是這樣吧

上圖是當球與線發生碰撞的瞬間,綠色線的方向及長度,代表球的前進向量。

上圖是表示,我們可以將綠色那條向量拆解成藍色那兩條相互垂直的向量,並注意一點,其中一條與碰撞目標線(粗黑)是平行的。

上圖,當發生碰撞後,與目標線垂直的那條向量會變反相,而平行的不變。反應後,可組成一條新的向量,即是球碰撞後的反射軌跡。

所以這部份的解題步驟應該是:
球的前進向量-->拆成與目標線平行、垂直兩部份-->將垂直那條反向-->組成新的前進向量
但是由於我們目前在as中,位移的方式是以xmov、ymov這種方式,而不是直接用向量的方式,所以這部份就變得更麻煩些:
球原本的xy位移量-->各別拆成與目標線平行、垂直兩部份-->將垂直那部份反向-->組成新的x y位移量
//函式:反應
function ballLineReaction(tempLine, point, x, y) {
//起始設定
var lineDecay = tempLine.lineDecay;
var alpha = tempLine.radian;
var cosAlpha = Math.cos(alpha);
var sinAlpha = Math.sin(alpha);
//取得球的x向量及y向量
var vyi = point.ymov;
var vxi = point.xmov;
//轉換為與目標線垂直之向量
var vVp = vyi*cosAlpha-vxi*sinAlpha;
//轉換為與目標線平行之向量
var vHp = vxi*cosAlpha+vyi*sinAlpha;
//反轉垂直向量
var vVfp = -vVp*lineDecay;
var vHfp = vHp;
//將之轉換回xy位移量
var vVf = vVfp*cosAlpha+vHfp*sinAlpha;
var vHf = vHfp*cosAlpha-vVfp*sinAlpha;
//將結果傳回給point
point.xmov = vHf;
point.ymov = vVf;
point.tempx = point.x+point.xmov;
point.tempy = point.y+point.ymov;
}

*這部份的as code,原書中的變數名稱與我的並不相同,原因是原書中都是用x y來取名,但我在看的時候卻常跟x y位移量搞混,所以都改成v(垂直)跟h(水平)


第五節 Conservation of Momentum and Energy(應該是動量及動能守恆吧?)
*這部份的概念還要多參考一些物理書才行…先挑範例來練習吧
*我的物理知識有限,如有錯,請務必告知

列出會用到的公式
p = mass*v; //動量公式
kinetic energy = (?)*mass*speed*speed //動能公式

假如做兩物體的碰撞
p1i = m1*v1i ; //物體1動量,碰撞前(i代表碰撞前)
p2i = m2*v2i //物體2
Pi = p1i+p2i //碰前動量總和

ke1i = (1/2)*m1*v1i*v1i //物體1碰撞前動能
ke2i = (1/2)*m2*v2i*v2i //物體2
KEi = ke1i+ke2i //碰撞前動能總和

////以下為碰撞後(f代表碰撞後)
p1f = m1*v1f
p2f = m2*v2f
Pf = p1f+p2f

ke1f = (1/2)*m1*v1f*v1f
ke2f = (1/2)*m2*v2f*v2f
KEf = ke1f+ke2f

///由於動量動能守恆
Pi = Pf = P
m1*v1i+m2*v2i = m1*v1f+m2*v2f
KEi = Kef
(1/2)*m1*v1i*v1i+(1/2)*m2*v2i*v2i = (1/2)*m1*v1f*v1f+(1/2)*m2*v2f*v2f

///整理後可導出
//**這個整理過程需要用到 (A*A-B*B)=(A-B)*(A+B)
v1i-v2i = v2f-v1f
V = v1i-v2i

//所以

v1f = v2f-V

//而我們最後需要的是

v2f = (P+V*m1)/(m1+m2)
v1f = v2f-v1i+v2i


(天啊,自己都快不知道自己在寫啥了,還是快找範例來練吧)


第六節 兩方塊的水平碰撞反應
範例檔:collisionReaction_5.fla

如果不計較物理理論的話,就直接引用上面的公式,放到as code中使用吧
在碰撞上,直接引用上一章的筆記中的矩形碰撞,而在偵測到碰撞後,呼叫這個函式就行了
//函式:碰撞反應
function reaction(a, b) {
var m1 = a.mass;
var m2 = b.mass;
var v1i = a.xmov;
var v2i = b.xmov;
var V = v1i-v2i;
var P = m1*v1i+m2*v2i;
//求碰撞後,物體b之速度
var v2f = (P+m1*V)/(m1+m2);
//求碰撞後,物體a之速度
var v1f = v2f-v1i+v2i;
//將答案傳回給各obj
a.xmov = v1f;
b.xmov = v2f;
//更新各obj之temp位移值
a.tempx = a.x+a.xmov;
b.tempx = b.x+b.xmov;
}

只看程式的話,好像滿簡單的。但背後的理論,還是不懂
(問了一些朋友,發現還會扯出一些"功"、"力"之類的,愈聽愈模糊-_-")


第七節 多個方塊的水平碰撞反應
範例檔:collisionReaction_6.fla

承接前一節範例,做成四個方塊的碰撞,並將四個方塊的質量設成不同的,可得到不同的碰撞反應速度
基本上,上一節的會寫的話,這節就沒什麼問題了,只是為了方便撰寫,用了一些回圈。
這個範例跟書中所附的不太一樣,因為我照書裏的寫(看懂別人寫的,比自己寫,來得更花時間)
不過原書的範例只花了七十多行,我倒寫了100行整-_-"。有書的人可對照參考看看…

第八節 球-球的碰撞反應
範例檔:collisionReaction_7.fla

用簡單點的方式來做筆記好了
1.球的碰撞偵測引用之前做的"不受限制的球球碰撞偵測"
2.碰撞反應則用類似之前做的"球與線的碰撞反應",將球與球的碰撞面當成之前的碰撞線,就差不多了:
將前進向量轉換為與碰撞面平行及垂直兩種。
將兩球的垂直的向量,以之前的一維碰撞的方式去解。平行的先不管。
垂直的算出結果後,再回來跟平行的結合並轉換回新的 x y 向量。


第九節 重力模擬
範例檔:collisionReaction_8.fla

照理說,碰撞反應教到這裏就差不多了,但書中最後還加了一個重力模擬的練習,拿來練練也不錯

我再次以自己的方法改寫,直接引用上一節的檔案,加了兩個東西
*因為要加很多球,所以先把大部份函式改以迴圈來跑
1.加上球跟四周的碰撞,這部份很簡單,當發現球超出場景時,反轉他碰到牆的那個向量
2.加上引力公式(又開始物理課了)
萬有引力公式: F=G*m1*m2/(d平方) F:引力,G:萬有引力常數,m1m2:兩物各別質量,d:兩物體距離
加速度公式:F=m*a ,所以 a1=F/m1 a2=F/m2 a:加速度
所以引力的函式會變這樣
//引力
function gravitation() {
for (var i = 1; i< =game.numBalls; i++) {
for (var j = i+1; j<=game.numBalls; j++) {
var ballA = eval("game.ball"+i);
var ballB = eval("game.ball"+j);
var dx = ballA.xpos-ballB.xpos;
var dy = ballA.ypos-ballB.ypos;
var d = dx*dx+dy*dy;
var F = G*ballA.mass*ballB.mass/d;
ballA.amov = F/ballA.mass;
ballB.amov = F/ballB.mass;
var angle = Math.atan2(dy, dx);
var sinAng = Math.sin(angle);
var cosAng = Math.cos(angle);
ballA.xmov -= ballA.amov*cosAng;
ballA.ymov -= ballA.amov*sinAng;
ballB.xmov += ballB.amov*cosAng;
ballB.ymov += ballB.amov*sinAng;
}
}
}

另一點特別要注意的是,由於這個範例又有引力又有碰撞等等因素,所以要特別注意資料更新的順序,否則會出現球跟球疊在一起,或是球卡在牆壁等靈異現象

後來回去看書中範例,發現它反而是用較簡便的寫法,就是沒有用 "不受frame限制"的那種…(被騙了)

另一個未解決的疑點,就是我寫的這個範例,必需以player6 做publish才能正常,若以player7做publish會在球碰到球後當住,語法都檢查過了,但還是不知原因…希望有人能給予指點…

讀後心得
這裏算是我個人的心得,與各位同好分享之
看到這邊總算看完這兩章了,我想這裏教的碰撞偵測及反應已可應付許多需要碰撞功能的遊戲了。
不過理論歸理論,真的用起來的實用性如何,倒還沒試過,(我指真的用在要開發的遊戲上)
例如一些動作遊戲在執行時,不會是只有一、兩個或三、四個物件要測碰撞的,可能是數十個或以上。
那麼跑起來的速率如何呢?跟最簡單的hitTest比呢?
我希望在下一個遊戲作品中確實用進去看看。
下個章節是要教tile-based的遊戲製作,很想繼續看下去,但最近要趕論文,只好暫時作罷 (-_-")

*關於物理方面的知識,個人推薦兩本書:

針對開發電玩程式會用到的物理知識做講解,天瓏有賣,600多。

.一生受用的公式
應該可以當方便的小手冊,天下網站賣144。

沒有留言: