第七章物理引擎
第七章物理引擎
?家對下??款?常流?的游戲?定是?熟能詳,如”憤怒的?鳥”,”超級?柴??爾夫”,”神仙道”。它們背后都是靠物理引擎驅動的。Cocos2d?來描述?維世界,cocos2d?持Box2d和chipmunk,Box2d是?C++寫的,chipmunk是?c語?寫的,相對??,Box2d更流??些,因此本書主要接受Box2d,對chipmunk只稍作介紹。有興趣的同學可以??下去學習。
7.1 物理引擎
7.1.1 物理引擎的基本概念
Box2D是?C++寫的。開發者是Erin Catto,他從2005年開始就在每?屆的Game DevelopersConference(GDC)上進?關于物理模擬的演講。2007年的9?,他公開發布了Box2D引擎。從那以后,Box2D的開發?作?直很活躍。因為很受歡迎, 所以cocos2d整合了Box2D與之?起發布。 Box2D是?個?于游戲的剛體仿真庫。程序員可以在他們的游戲?使?它。它可以是物體的運動更加可信,讓游戲看起來更有交互性。從游戲的視覺來看,物理引擎就是?個程序性動畫的系統,?不是有動畫師去移動你的物體。?頓就是你的導演。 Box2D 是?可移植的 C++ 來寫成的。引擎中定義的?部分類型都有 b2 前綴,可以把它和我們游戲中的其他元素區分開來。
Box2D中的基本對象:
剛體(rigid body):?塊?分堅硬的物質,它上?的任何亮點之間的距離都是完全不變的。它就像鉆??樣堅硬。我們也可以理解為物理學中的質點,只有位置,沒有??,它?可以區分為以下?類:
? 靜態剛體:靜態剛體沒有質量,沒有速度,只可以?動來改變他的位置。
? 棱柱剛體:棱柱剛體沒有質量,但是可以有速度,可以??更新位置。
? 動態剛體:動態剛體有質量也有速度。
在以后的討論中我們將?物體(body)來代替剛體。
形狀(shape): ?塊嚴格依附于物體(body)的2D碰撞集合結構(collision geomerty)。形狀具有磨擦(friction)和恢復(restitution)的材料性質。形狀可以通過關節添加到物體上。
夾具(fixture): ?個固定裝置將?個形狀捆綁到?個body上,并添加材料屬性,例如密度,摩擦?,恢復等。
約束(constraint): ?個約束就是消除物體?由度的物理連接。在 2D 中,?個物體有 3 個?由度。如果我們把?個物體釘在墻上(像擺錘那樣),那我們就把它約束到了墻上。這樣,此物體就只能繞著這個釘?旋轉,所以這個約束消除了它 2 個?由度。還有?種不須你創建的接觸約束,?個防?剛體穿透,以及?于模擬摩擦和恢復的特殊約束。你永遠不必創建?個接觸約束,它們會被Box2D創建。
關節(Joint): 它是?種?于把兩個物體或多個物體固定到?起的約束。Box2D?持的關節類型有:旋轉,棱柱,距離等等。關節可以?持限制和馬達。
? 關節限制:?個關節限制(joint limit)限定了?個關節的運動范圍。例如中標的鐘擺只能在某個范圍?度內運動。
? 關節馬達(joint motor)?個關節馬達能依照關節的?由度來驅動所連接的物體。例如你可以??個齒輪來驅動鐘擺的旋轉。
世界(world): 世界是遵循物理的空間,以上的所有都存在于世界中,可以創建多個世界,但很少這樣?。創建世界需要兩個步驟,?是?成重?向量,?是根據重??成世界對象。
7.1.2物理引擎的局限性
物理引擎有它??的局限性:它們必須在模擬效果時使??些捷徑,也就是說簡化物體的復雜性,因為
真實世界過于復雜,完全放到物理引擎中進?模擬是不可能的。這就是為什么要使?剛體的原因。在某些極端情況下,物理引擎有可能會捕捉不到某些已經發?的碰撞 – 例如,當剛體以很快的速度移動時,?個剛體可能直接穿透另?個剛體。雖然在量?物理學中這樣的穿透情況會發?, 但是我們看到的真實世界中的物體是不會相互穿透的。
剛體有時候會相互穿透卡在?起,特別是在使?了關節將它們連接在?起以后。卡在?起的剛體會努?要分開,但是為了滿?關節的連接要求,它們?不得不卡在?起,結果是卡在?起的剛體會產?顫動。
我們也可能碰到游戲運?的問題。如果我們在游戲?使?了很多剛體,你永遠
不會知道這些剛體相互作?后的最終結果。最終,有些玩家會把??卡死在剛體中,或者他們也可能會發現如何利?物理模擬的漏洞,跑到游戲中他們本來不應該去的區域。
7.2 Box2d設計思路
7.2.1 內存管理
Box2D的許多設計都是決策都是為了能夠快速的使?內存。
Box2D不傾向分配?量的?對象(50-300字節)。這樣通過malloc或new在系統堆(heap)上分配內存效能太低效,并且容易產?內存碎?。
Box2D的解決?案是使??型對象分配器(SOA),SOA維護了許多不定尺?的可?長的池,當有內存分配請求的時候,SOA會返回?塊最匹配的內存,當內存釋放掉以后,它會回到池中。這些操作都?分快速,因?只會產?很?的堆流量。
因為Box2D使?了SOA,所以你永遠也不必去new?malloc物體,形狀活關節,都只需要分配?個b2World,它為你體總了創建物體,形狀和關節的??。
7.2.2 ??和定義
如上所述,內存管理在Box2D API的設計中擔當了?個中???,所以當你創建?個b2Body或?個b2Joint的時候,你需要?b2World的??函數。
下?是創建函數
1 b2BodyDefcontainerBodyDef;
2 b2Body*containerBody = world->CreateBody(&containerBodyDef);
下?是對應的摧毀函數:
world->DestroyBody(&containerBody);
7.2.3 單位
Box2D使?浮點數,所以必須使??些公差來保證它們正常?作。這些公差已經被調諧得適合?,千克,秒單位。尤其是,Box2D被調諧的能良好的處理0.1到10?之間的移動物體,這意味著從茶杯,粉筆盒到卡車??的對象都能良好的?作。但是在你創建的Box2D世界中的剛體的??限定在越接近1?越好。不過這并不意味著你不能有長度?于0.1?的剛體,或者長度?于10?的剛體。但是,太?或者太?的剛體很可能會在游戲運?過程中產?錯誤和奇怪的?為。
作為?個2D物理引擎,如果能夠使?像素作為單位是很誘?的,很不幸,那將導致不良模擬,會造成古怪的?為。?個200像素長的物體在Box2D看來就有45層建筑物那么?。想象?下使??個被調諧好的玩偶和?桶去模擬?樓?廈的運動,那?定很怪異。
我們?定習慣了,?像素來計算屏幕中的位置,因為這樣對我們來說更為直觀,我們可以通過下?的?式進?b2Vec2和CGPoint的轉變,這意味著你不能夠在需要CGPoint的地?使?b2Vec2,反過來也不?。?且,Box2D?的點需要轉換成?為單位,或者從?為單位轉換回以像素為單位。為了避免出錯,?如忘記轉換單位,或者打錯了字,或者把x軸坐標使?了兩次,我強烈建議你把這些重復的轉換代碼封裝到?法中去,?先定義?個轉變基數:
#definePTM_RATIO 32
PTM_RATIO?于定義32個像素在Box2D世界中等同于1?。?個有32像素寬和?的盒?形狀的剛體等同于1?寬和?的物體。在Box2D 中,4x4像素的??是 0.125x0.125。你可以通過PTM_RATIO把剛體的尺?設置成最適合Box2D的尺?, ?PTM_RATIO設置為32,對于擁有1024x768像素的iPad來說也是很合適的。
-(b2Vec2)toMeters:(CGPoint)point
{undefined
return b2Vec2(point.x / PTM_RATIO,point.y / PTM_RATIO);
}
-(CGPoint)toPixels:(b2Vec2)vec
{undefined
return ccpMult(CGPointMake(vec.x, vec.y),PTM_RATIO);
}
這樣我們就可以很容易的進?b2Vec2和CGPoint的轉變
1 CGPoint point = CGPointMake(100, 100);b2Vec
2 vec = b2Vec2(200, 200);
2 CGPoint pointFromVec; pointFromVec = [lftoPixels:vec];
3 b2Vec2 vecFromPoint; vecFromPoint = [lftoMeters:point];
7.2.4 ?戶數據
b2shape,b2Body和b2Joint類都允許你通過?個void指針來附加?戶數據。這在你測試Box2D數據結構,以及你想把它們聯系到??的引擎中是較為?便的。
舉個典型的例?,在??上的剛體中附加到??的指針,這就構成了?個循環引?。如果你有??,你就能得到剛體,如果你有剛體,你就能得到??。
這是?些需要?戶數據的案例:
? 使?碰撞結果給??施加傷害。
? 當玩家進??個包圍盒時播放?段腳本事件。
? 當Box2D通知你?個關于即將摧毀時訪問?個游戲結構。
7.3世界(world)
7.3.1 什么是世界
b2World類包含著物體和關節,它管理者物理模擬的????,并允許異步查詢(就想AABB查詢)你與Box2Dderek?部分交互都將通過b2World對象來完成。?個世界是?個物理引擎的開始,我們從創建?個世界開始,講逐步告訴你怎么創造?個物理引擎,?個??定義的世界。
7.3.2 創建和摧毀?個世界
創建?個世界和摧毀?個世界很簡單,你只需要提供?個重?向量和是否允許物體休眠。
要創建或摧毀?個世界你需要使?new:
1 -(id) init
2 {undefined
3 if((lf = [super init]))
4 {undefined
5 b2World*world;
6 b2Vec2gravity = b2Vec2(0.0f, -10.0f);
7 bool allowBodiesToSleep =true;
8 world = new b2World(gravity,allowBodiesToSleep);
9 }
7.3.3 使??個世界
7.3.3.1 模擬
世界類?于驅動模擬。也就是說我們可以決定可以多長時間刷新物理世界,它包括物體的速度和位置等信息的刷新。我們需要制定?個刷新的時間間隔和迭代次數。
例如下?的代碼是按制定的最快的速度刷新,每次刷新是速度會迭代8次,?位置的計算迭代?次。
-(void) update:(ccTime)delta
{undefined
float timeStep = 0.03f;
int32 velocityIterations = 8;
int32 positionIterations = 1;
world->Step(timeStep,velocityIterations, positionIterations);
}
Box2D建議的刷新的頻率是固定的。
那為什么我們還要允許我們??定義這些參數呢?當我們的游戲運?負擔?較輕的時候,我們可以給?戶?個較?的刷新頻率,這樣?戶就能獲得更好的體驗;當我們的游戲負擔?較重的時候,我們可以使??個較低的刷新頻率,已獲得?個?戶還算滿意的游戲體驗。
7.3.3.2 掃描世界
如上所述,世界就是?個物體和關節的容器,當我們刷新世界的時候,肯定是想讓世界的物體或者關節發?某種變化。你可以獲取世界中所有物體和關節遍歷它們。例如,你可能需要需要改變某個精靈的位置或者喚醒世界中的所有物體,
我們在-(void) update:(ccTime)delta?法中添加如下代碼
for (b2Body*body = world->GetBodyList(); body != nil; body = body->GetNext())
{undefined
CCSprite* sprite =(CCSprite*)body->GetUrData();
if (sprite != NULL)
{undefined
// update the sprite's position to wheretheir physics bodies are
sprite.position = [lftoPixels:body->GetPosition()];
float angle = body->GetAngle();
}
}
7.4物體(Body)
靜態物體(b2_statiBody)
?個靜態物體不會在模擬中?種,并且它?動起來就像其有?限的質量。內部原因講,Box2D將質量存儲為零,靜態物體能被?戶?動操作移動。靜態物體含有零向量,不會與其它靜態物體或者運動的物體碰撞。
運動但不受?物體(b2_kinematicBody)
運動但不受?物體憑借向量可以在模擬中運動,它們不受?的作?。可以通過?戶?動操作?做運動,但通常情況下,運動但不受?物體通過設置其向量來操作移動。其?為看起來也好像有?限的重量,但是,Box2D將質量存儲為零,運動但不受?物體也不會與靜態物體或者運動物體碰撞。
動態物體(b2_dynamicBody)
動態的body被完全模擬,他們可以通過?戶?動操作?移動,但通常情況下,他們在?的作?下移動,動態body可以與任何類型的body碰撞,?個動態的body旺旺是有限制的,必須為?零質量。如果你想把動態body的質量設為零,它將?動獲得?千克的質量。
7.4.1 物體定義
前?我們已經創建了?個世界,現在我們創建?個物體綁到世界上。
在物體創建之前,你必須創建?個物體定義(b2BodyDef)來初始化物體所需的數據。
我們先來熟悉?個b2BodyDef的各種屬性
b2BodyDefcontainerBodyDef;
containerBodyDef.position.Set(0.0f,2.0f);
containerBodyDef.angle= 0.25f*b2_pi;
containerBodyDef.linearDamping = 0.0f;
containerBodyDef.angularDamping=0.01f;
containerBodyDef.allowSleep= true;
containerBodyDef.awake= true
containerBodyDef.urData= &myaction;
containerBodyDef.fixedRotation= true;//固定選裝
containerBodyDef.bullet= true;
物體類型(type)屬性:在初始化?個物體的時候,你就應該確定好改物體的類型,靜態物體,動態但不受?物理還是動態物體。并輕易不要修改它。
位置(position)和?度(angle)屬性:定義物理之后,我們可以初始化?個位置和物體的?度,?不是所有的物體從原點建?,?后移到你所需要的位置上?。
阻尼(linearDamping和angularDamping):阻尼是?來減?物體的速度的,阻尼與摩擦不同,因為只有接觸才回產?摩擦,阻尼也不是摩擦的取代,這兩個效果要?起使?。阻尼參數范圍是0到?窮?,0是沒有阻尼,?窮就是滿阻尼。
休眠參數(allowSleep和awake):模擬是?常昂貴的,我們應當盡量減少模擬物體,當?個物體休息時,我們應當停?他們的模擬。?彈(bullet):有的時候,在同?個時間有?量的剛體在運動,我們肯定不希望這些物體能夠相互穿來穿去的,這被稱作隧道效應。
默認情況下,Box2D會通過連續碰撞檢測來防?動態物體穿越靜態物體。但動態物體之間是不使?連續碰撞檢測的,這是為了保持游戲的性能。告訴移動的物體在Box2D中被稱為?彈(bullet),?彈能夠檢測到碰撞,?不會引起穿壁?過的情況。
?戶數據(urData):?戶數據是個空指針,它給你提供了?個掛鉤來將你的應?程序對象連接到物體上,對所有物體的?戶數據,你需要?個?致的對象類型。
7.4.2 創建物體
上?我們已經知道了如何定義?個物體的屬性,前?我們已經說過,所有的物體創建和銷毀都是有世界(World)來完成的,這使得世界可以通過?個?效的分配器來創建物體,并且把物體添加到世界上。我們在上?的-(id) init添加如下代買,把創建?個物體并綁定到世界(World)上
b2BodyDef containerBodyDef;
b2Body* containerBody =world->CreateBody(&containerBodyDef);
7.4.3 使?物體
創建?個物體之后,?般我們不應該改變它的屬性,?應該遵循物理規則使其產?變化,但是?些特殊情況,我們也可以讀取和修改其屬性。這些可修改的屬性有:質量數據,狀態信息,位置和速度等。
使?最多的是通過?和沖量等改變物體的運動。
你可以對?個物體應??,扭矩,以及沖量。當應??個?或者沖量時,你需要提供?個世界位置。這常常會導致對質?的?個扭矩。void ApplyForce(const b2Vec2& force,const b2Vec2& point);
void ApplyTorque(float32 torque);
void ApplyAngularImpul(float32 impul);
應??,扭矩?或沖量會喚醒物體,有時這是不合需求的。例如,你可能想應??個穩定的?,并允許物體休眠來提升性能,這時,你需要這要來改變物體的屬性。
if(containerBody->IsSleepingAllowed() ==fal)
{undefined
containerBody->ApplyForce(myForce, myPoint);
}
7.5形狀
形狀就是物體上的碰撞?何結構,另外形狀也?于定義物體的質量。也就是說,你來指定密度,Box2D可以幫你計算出質量。