Cocos2d-x學習筆記(十五)-------->物理引擎,cocos2d-x學習筆記

來源:互聯網
上載者:User

Cocos2d-x學習筆記(十五)-------->物理引擎,cocos2d-x學習筆記
物理引擎
Cocos2d-x引擎內建了兩種物理引擎,它們分別是Box2DChipmunk,都是非常優秀的2D物理引擎,而且x引擎將它們都內建在SDK中。Box2D使用較為廣泛,在這裡選擇Box2D來進行學習。
物理引擎類比的內容重力:在遊戲中類比重力加速度,當遊戲中人物跳躍起來後會受到重力影響而向下移動,在沒有地面的情境,人物和物體會由於重力而做自由落體運動。牽引力(動力):在遊戲中比如汽車的引擎,人物本身能夠提供向前進行的動力,這種牽引力是持續不斷地作用在物體上的,物體因此可以向作用力的方向移動。摩擦力:物體在地面等接觸面上移動時,會受到摩擦力的影響,它可以使正在運動的物體由於摩擦力的作用而停下來。衝擊力:比如爆炸會產生一次性的衝擊力,會對爆炸範圍內的物體產生一瞬間的力的作用,使其運動起來。碰撞檢測:當一個物體與另一個物體碰撞後,兩個物體會因為碰撞發生作用力與反作用力,會讓其之前的運動受到影響。還有浮力關節連結等等物理概念。
Box2D簡介Box2D是由C++開發的一款輕量級的二維剛體模擬庫,主要用於編寫2D遊戲,開發人員可以使用它讓遊戲中的物體運動起來更真實。讓遊戲世界更具互動性。Box2D物理引擎是一個程式性動畫系統。做動畫常有兩種方法:一種是預先準備好動畫所需的映像資料,比如某種格式的2D圖片,再一幀一幀地播放。這種預先準備的可稱為資料性動畫。另一種是以一定方法,動態計算齣動畫所需的資料,比如移動後的新位置、旋轉的角度等等,根據這些資料再進行繪圖。這種動態計算的可稱為程式性動畫。Box2D就是用物理學的方法,推匯出遊戲世界物體的位置、角度等資料。而Box2D也僅僅是推匯出資料,至於得到資料之後怎麼處理就是開發人員自己的事情了。
Box2D的一些基本對象物理世界world:一個物理世界就是各種剛體bodies夾具fixtures約束constraints等物理引擎中基本對象相互作用的集合。所有的物理對象都是在物理世界已經建立好的基礎上,在物理世界中產生的。物理世界具有一個範圍,在2D座標系中,物理世界的範圍就是一個矩形地區,地區內的物理對象可以相互作用,發生物理碰撞等影響,一旦物理對象到了地區之外,將不再進行物理運算,不再產生任何物理作用。Box2D支援建立多個世界,但這通常沒有必要。物理世界是Box2D引擎最為重要的對象,遊戲必須要持有物理世界對象,這樣才能訪問物理世界中的各種對象,知道它們的狀態,然後將這些狀態更新到遊戲介面中反映出各種類比的物理現象。剛體rigid body:大多數遊戲對象在物理世界中都被抽象成為剛體對象,它是物理世界中十分堅硬的物質,物理引擎假定剛體都是不會發生形變的,它上面任意兩點之間的距離都保持不變。在Box2D物理引擎中,b2Body類就是代表剛體的類型。在設計實現物理遊戲時,剛體通常都對應著遊戲中的一個具有具體外形的角色。剛體在Box2D中主要分為兩大類,一類是可以移動位置或者旋轉的動態剛體,這種剛體通常用於表示遊戲中的活動物體;另一類是位置無法移動和旋轉的靜態剛體,這種剛體通常用於表示遊戲中的地面平台等靜物。夾具fixture:每個剛體都需要定義一個或者多個夾具,夾具是一個屬性容器,它具有形狀屬性shape密度屬性density摩擦屬性friction恢複屬性restitution。當一個剛體具有了夾具之後,它就可以參與物理世界的碰撞檢測,摩擦力運算和彈力運算了。形狀shape2D幾何外形對象,比如圓形circlr或者多邊形polygon。形狀定義好之後會被附加到某個夾具之上,作為夾具的外形屬性存在,它是夾具的重要組成部分,傢具在剛體碰撞運算時會通過形狀來進行檢測。形狀類中儲存的主要是形狀的幾何資料資訊,比如一個圓形circle主要是儲存它的半徑資訊,只要知道了半徑就能知道圓形的具體大小;另一個比較常用的形狀是四邊形rect,它主要記錄的是四邊形的寬度和高度資訊。關節joint:關節就是種約束,用於將兩個或多個剛體固定到一起。Box2D支援不同的關節類型——轉動revolute稜柱prismatic距離distance等。比如卡通人物的手臂運動,就可以定義一個和人類一樣的肘關節,關節兩端是上臂和前臂兩個剛體。一些關節可以有限制limits馬達motors關節限制joint limit:關節限制限定了一個關節點運動範圍。例如人類的胳膊肘只能在某一角度範圍內運動。關節馬達joint motor:根據關節的自由度,關節馬達可以驅動關節所串連的物體。例如你可以使用一個馬達來驅動一個肘的旋轉。
在遊戲中引入Box2D物理世界:因為x引擎內建了Box2D物理引擎,所以需要物理引擎的地方只要引入“Box2D/Box2D.h”標頭檔即可,以下代碼就是建立物理世界,也就是初始化Box2D物理引擎的過程,這個過程都是放在遊戲情境初始化階段,把物理世界對象作為遊戲世界的一部分完成初始化過程。

//定義重力加速度b2Vec2 gravity;//設定垂直方向的重力加速度gravity.Set(0.0f, -9.8f);/*使用剛剛定義好的重力加速度產生物理世界對象,這樣世界中的所有對象都會受到重力加速度的影響*/b2World* phyWorld = new b2World(gravity);//物理世界的對象都參與碰撞檢測,無休眠對象phyWorld->SetAllowSleeping(false);//連續碰撞檢測,避免發生物體穿過另一個物體的事件phyWorld->SetContinuousPhysics(true);//設定碰撞監聽器phyWorld->SetContactListener(listener);
通過以上代碼,就可以在 x引擎中建立一個物理世界,以上代碼的最後一步,用於設定物理世界中各種物體碰撞的監聽對象—— listener,它是 b2ContactListener類型。在物理引擎捕捉到世界中的物體對象發生碰撞後,會使用碰撞監聽器 b2ContactListener的回調方法,來實現碰撞的發現和響應功能。我們要做的就是定義好一個碰撞檢測器,實現它的碰撞回呼函數。有以下函數需要實現。 virtual void BeginContact(b2Contact* contact):碰撞開始時的回呼函數,一般簡單的碰撞檢測使用;
virtual void EndContact(b2Contact* contact):碰撞發生後的回呼函數,一般簡單的碰撞檢測使用;
virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold):碰撞求解前的回呼函數,求解就是指計算碰撞產生的衝擊力,需要計算碰撞衝擊力造成的破壞等效果時,需要使用此回呼函數;
virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse):碰撞求解後的回呼函數,需要計算碰撞衝擊力造成的破壞等效果時,需要使用此回呼函數。

這四個回調方法中,前兩個功能有限但使用起來簡單,後兩個提供的資訊量大,但使用起來比較複雜,這個要根據遊戲的具體要求而定,如果我們的遊戲過程對物理要求不高,僅僅是實現碰撞檢測功能,那麼我們主要使用 BeginContact(b2Contact* contact)這個回呼函數就足夠了,如果我們要處理碰撞之前和碰撞之後的效果,根據碰撞中產生的相互作用力來計算物理碰撞後的移動,則我們必須好好的利用全部這四個函數,它們聯合作用起來,可以類比出比較真實而複雜的物理碰撞效果。在遊戲初始化時引入 Box2D物理世界,然後實現碰撞回呼函數之後,我們的物理世界初始化工作就完成了,剩下的工作是根據遊戲要求,預先產生或者在遊戲過程中動態產生各種物理對象。比如剛體對象。
定義物體對象,實現重力效果:在物理類比遊戲當中,一般都會有大量的剛體存在於這個物理世界內。有時候這些剛體是在遊戲初始化時建立的,他們有位置、密度、體積等預製好的屬性;而另一種情況是根據遊戲過程即時動態地產生剛體,並為剛體設定位置等屬性。 Box2D是一個高效的物理引擎,所以即時動態產生剛體的速度非常快,只要數量不是非常巨大,就不會影響遊戲的運行速度,剛體通常都會對應這個遊戲中的某個角色或者是角色的一部分,比如在飛行射擊遊戲中,飛機的身體就可以用一個剛體或者多個剛體的組合來代表,剛體就是遊戲角色在物理世界的抽象,剛體碰撞的物理變化最終還要反饋到遊戲中的角色身上。
//首先產生b2BodyDef這個結構體的執行個體b2BodyDef spriteBodyDef;//指定剛體定義的類型是動態剛體,表明剛體是可以在物理世界中移動的spriteBodyDef.type = b2_dynamicBody;//設定剛體定義的初始位置spriteBodyDef.position.Set(5.0f,5.0f);//接下來使用spriteBodyDef對象來產生真正的剛體Bodyb2Body* spriteBody = phyWorld->CreateBody(&spriteBodyDef);//產生一個矩形形狀,定義大小範圍b2PolygonShape spriteShape;spriteShape.SetAsBox(10.0f, 10.0f);//接下來產生剛體Body將要使用的夾具對象b2FixtureDef spriteShapeDef;//指定夾具的外形就是剛剛產生的矩形spriteShapeDef.shape = &spriteShape;//設定其物理密度spriteShapeDef.density = 10.0f;//設定自己所屬的碰撞組spriteShapeDef.filter.categoryBits = 0x0010;//第2組//指定自己會與哪個組發生碰撞spriteShapeDef.filter.maskBits = 0x0001;//第1組//使用定義好的夾具產生剛體spriteBody->CreateFixture(&spriteShapeDef);
此時,物理世界中就有了一個剛體對象,這裡要強調的是剛體的夾具定義時的碰撞分組資訊,其中 filter.categoryBits把剛體的夾具定義在第2組,接下來的 filter.maskBits定義為第1組;這樣此剛體的夾具就會與處於第一組的剛體夾具發生碰撞,而其他組或者位於同一組的剛體夾具,即使有了接觸也不會發生碰撞事件,這是 Box2D物理引擎的碰撞分組篩選功能。這在遊戲中非常有用。在定義好這個剛體之後,在沒有設定它的位置時,它會預設出現在物理世界的原點,也就是座標 (0,0)的點。
實現物體的碰撞檢測: b2ContactListener:它是整個 Box2物理世界中發生碰撞的監聽以及響應類,所有發生在 Box2D物理世界中的碰撞時間都能夠被 b2ContactListener類型的監聽器檢測並在碰撞響應函數中被處理。通常我們都是將自己實現的遊戲世界作為碰撞檢測的介面實作類別,也就是說在定義某個我們自己的遊戲世界類(這裡我們假設將它稱為 World類)時,我們讓它繼承自 b2ContactListener,這樣 World類的執行個體對象就可以對物理世界的碰撞捕捉和處理了。 例如
class World :public b2ContactListener
對應物理世界的處理回呼函數也在此聲明,這個例子中我們只對發生碰撞那一刻的事件做響應,其他事件不做詳細處理,所以碰撞檢測的函式宣告如下。
//碰撞事件回呼函數virtual void BeginContact(b2Contact* contact);virtual void EndContact(b2Contact* contact){ B2_NOT_USED(contact);//關閉此事件,不做處理}virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold){B2_NOT_USED(contact);//關閉此事件,不做處理B2_NOT_USED(oldManifold);//關閉此事件,不做處理}virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse){B2_NOT_USED(contact);//關閉此事件,不做處理B2_NOT_USED(impulse);//關閉此事件,不做處理}


使用關節來串連剛體連接器:連接器可以使兩個或者多個剛體串連到一起,起到限制世界當中物體自身或物體之間的作用。
距離連接器( Distance Joint):最為常見也是最為簡單的連接器,是通常所說的在兩個剛體上兩個點之間保持一定距離的距離連接器。當你指定一個距離連接器時,相應的兩個剛體應該已經在應有的位置上了。然後在全局座標系中指定兩個錨點定點,第一個錨定點串連body1,第二個錨定點串連body2。這些點代表著應該保持的距離的常量。這樣不論兩個物體怎樣運動,它們之間都會保持著固定的距離,就像使用一隻杆子串連了這兩個物體一樣。距離連接器也可以變成軟的,就像串連一個彈簧一樣,在定義中通過調節 頻率( frequency)和阻 尼率( damping ratio)兩個常量來取得柔軟的效果,以下是定義一個彈簧效果的距離連接器。
b2DistanceJointDef jointDef;jointDef.Initialize(body1, body2, body1->GetPosition(),body2->GetPosition());jointDef.collideConnected = true;jointDef.frequencyHz = 4.0f;jointDef.dampingRatio = 0.5f;jointDef.length = 10;phyWorld->CreateJoint(&jointDef);
距離連接器的應用場合非常廣泛,固定距離連接器可以類比翹翹板、捆綁物體的繩子這些物理現象;軟性的連接器則可以用來類比彈跳板、橡皮筋等物理現象。

旋轉連接器( Revolute Joint):旋轉連接器同時作用於兩個剛體,並使兩個剛體共用同一個錨定點,經常稱之為 鉸鏈點( hinge point)。相對於兩個物體的旋轉來說,旋轉連接器有一個自由度範圍。這個角度稱為 串連角( joint angle)。
定製一個旋轉串連,我們需要在世界中提供兩個剛體和一個簡單的錨定點。初始化方法假設物體已經在正確的位置。在以下例子代碼中,兩個剛體通過旋轉連接器以第一個物體的質心作為 鉸鏈點( hinge Point)串連在一起。當 bodyB逆時針旋轉的時候,轉動連接器的角度為正值。就像 Box2D中的所有其他角一樣,旋轉是以弧度為基準。一般來說,旋轉連接器使用 Initialize()方法建立完成之後,旋轉連接器的角度為零,和兩個物體當前的角度無關。在一些場合下你可以希望控制 串連角( joint angle),以下代碼給出了旋轉連接器的建立和串連角的限制設定:
b2RevoluteJointDef jointDef;jointDef.Initialize(body1,body2, b2Vec2(body1->GetPosition().x-15,body1->GetPosition().y+15));jointDef.lowerAngle = -0.5 * b2_pi;jointDef.upperAngle = 0.25 * b2_pi;jointDef.enableLimit = true;phyWorld->CreateJoint(&jointDef);
旋轉連接器的應用也非常廣泛,凡是涉及到旋轉開關的地方,都可以使用旋轉連接器來實現;比如:汽車的輪子。我們只要將動力或者扭矩作用在輪子剛體上讓連接器旋轉,汽車就可以向前或者向後移動了。

平移連接器( Prismatic Joint):平移連接器運行相關聯的兩個剛體沿著特定的座標軸進行平移。平移串連阻止相對旋轉。因此平移串連只有一個方向上的自由度。
b2PrismaticJointDef jointDef;jointDef.Initialize(body1,body2,body2->GetPosition(), body2->GetPosition());jointDef.lowerTranslation = -100.0f;jointDef.upperTranslation = 100.0f;jointDef.enableLimit = true;phyWorld->CreateJoint(&jointDef);
平移連接器應用場合在遊戲中也很廣泛:當一個物體與另一個物體在某個平面交叉移動時,就需要用到平移連接器,比如垂直升降的電梯,就可以使用平移連接器來類比實現。

Box2D調試渲染:在測試過程中,所有的剛體外形都應該是可見的,這樣我們才能觀測出各種物理碰撞等現象的詳細過程和結果,這就需要我們引入 GLESDebugDraw類。它是使用 OpenGLES底層繪圖方法,將剛體的外形準確繪製到螢幕上的功能類。 GLESDebugDraw類位於 Cocos2d-x SDKGLES-Render.h檔案中。所有如果遊戲需要使用到 Box2D物理引擎並且需要調試,就要將這兩個類也拷貝到遊戲的類檔案目錄下並引用這兩個檔案。在這兩個檔案中還定義了 b2Draw類,它提供了大多數關於繪製幾何圖形的抽象方法。 使用:首先要將 GLESDebugDraw類型的對象設定到物理世界對象上,然後確認有哪些內容需要被繪製,可以繪製的內容包括剛體的 外形質心關節AABB包圍盒(也就是能夠包圍剛體的矩形包圍盒)等內容。
debugDraw = new GLESDebugDraw();//這裡建立一個debug渲染模組phyWorld->SetDebugDraw(debugDraw);//設定uint32 flags = 0;flags += b2Draw::e_shapeBit;//形狀flags += b2Draw::e_aabbBit;//AABB塊flags += b2Draw::e_centerOfMassBit;//物體質心flags += b2Draw::e_jointBit;//關節debugDraw->SetFlags(flags);scheduleUpdate();//每一幀都會調用一個叫update的方法,進行重新整理螢幕
scheduleUpdate函數將會在每一幀中,調用一個 update的方法。這個方法可以這麼寫:
void HelloWorld::update(float dt){phyWorld->Step(0.03f,10,10);}
這樣,在每一幀的時候,都會進行螢幕重新整理,物理引擎中的內容都變為可見形式的了。

Box2D速度與效能注意事項:物理引擎實際上是比較佔用硬體資源的,因為引擎中大量的剛體碰撞檢測,各種作用力的效果計算都非常耗費 CPU運算時間。尤其是當剛體數量大量增長時, Box2D的計算量將成幾何級的增長。所以使用 Box2物理引擎時,一定要注意效能最佳化以及提高模擬度的幾個技巧。 (1)區分靜態剛體和動態剛體:如果有個物體可以使用靜態剛體來定義,那就一定不要使用動態剛體來定義它。因為靜態剛體僅僅進行碰撞檢測,不會考慮作用力對它的影響,相對於動態剛體來說,靜態剛體的計算量會小很多,可以減少 CPU的負擔。 (2) 啟動動態剛體休眠屬性:在物理引擎初始化時,可以通過設定是否允許動態剛體休眠來改善效能。如果設定允許動態剛體休眠,一些受到很小作用力或者沒有受到作用力的剛體,它們會保持靜止不動的狀態,此時它們會進入休眠 Sleep狀態。進入 Sleep狀態的剛體,將不會進行物理運算,這樣就可以減少運算時間,知道它們再次受到作用力處於運動狀態後,才會從休眠狀態被喚醒,繼續參與物理計算。
(3) 設定單位轉換參數:遊戲螢幕是以像素為單位長度的,而物理引擎是以米為單位長度的,這裡就存在一個米與像素的單位換算關係。物理引擎有一個合理的單位工作範圍,在 Box2D中,物體的大小最好在 0.1米10米之間,如果超過這個範圍,物體的表現可能會變得不真實。所以設計遊戲中的物體大小時,其像素單位也需要有一個合理的範圍。我們通常將換算值設定為 32,這樣在遊戲中 32*32像素大小的物體,在物理引擎中它就是 1米*1米大小,在這樣一個合理的大小範圍內, Box2D引擎的類比效果會比較真實。
(4) 設定子彈屬性:動態剛體有時會高速移動,如果剛體在兩幀之間移動的距離超過了碰撞物體的身長,那碰撞檢測就失去了檢測功能,出現剛體直接穿過障礙物的現象。所以如果某個剛體的運動速度很快時,需要設定此剛體為子彈類型,也就是高速移動的動態剛體類型,使用 SetBullet(true)函數。這樣的剛體在移動時會計算每一個單位的移動是否發生碰撞,不會發生直接穿過障礙物的現象。

複雜多邊形的代碼產生工具:當遊戲物體的形狀變得豐富而複雜時,手動書寫代碼定義物體外形是不現實的,這就需要藉助一些有效工具來定義物體的外形, vertexhelper軟體是一款開源免費的定點繪製工具。通過可視化的介面可以繪製出任意一種你想要的多邊形。而組成這些多邊形的頂點資料,將會以C++代碼的形式給出,經過拷貝粘貼,就可以將複雜的多邊形產生代碼放到自己的類中。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.