【Cocos2d-x】物理引擎使用入門
相關概念
什麼是物理引擎?
科學模型:科學研究中對事物的合理簡化。
物理引擎是一個計算機程式模擬牛頓力學模型,使用質量、速度、摩擦力和空氣阻力等變數。
可以用來預測這種不同情況下的效果。它主要用在科學模擬和電子遊戲中。
一般,物理引擎只負責物理計算,而不進行畫面渲染。
關於box2d
Box2D是一款免費的開源二維物理引擎,由Erin Catto使用C++編寫。它已被用於蠟筆物理學、憤怒的小鳥、地獄邊境等遊戲的開發。
Cocos2d-x本身已經整合box2d。
以上解釋來自維基百科。
剛體(body)
在box2d中物體即剛體。
剛體就是堅硬的物體,碰撞不會產生形變。
物體的型別有以下3種:
靜態物體:碰撞不會移動。質量為0。如:邊界、牆。
動態物體:碰撞會移動。
平臺物體:一直保持某一種運動的物體,如電梯會一直保持上下移動。
形狀(shape)
依附於剛體的2D碰撞幾何結構,形狀具有摩擦(friction)和恢復(restitution)的材料性質。
在Box2D中主要有以下兩種形狀:
1.圓形
2.多邊形
一個剛體的形狀,可以是由多個形狀組合而成。比如:人,頭是圓形,手、腳、身體都是矩形。
約束(constraint)
一個約束(constraint)就是消除物體自由度的物理連線。在 2D 中,一個物體有 3 個自由度。如果我們把一個物體釘在牆上(像擺錘那樣),那我們就把它約束到了牆上,而且此物體就只能繞著這個釘子旋轉,所以這個約束消除了它 2 個自由度。
接觸約束(contact constraint)
一個防止剛體穿透,以及用於模擬摩擦(friction)和恢復(restitution)的特殊約束。你永遠都不必建立一個接觸約束,它們會自動被 Box2D 建立。
關節(joint)
它是一種用於把兩個或多個物體固定到一起的約束。Box2D 支援的關節型別有:旋轉,稜柱,距離等等。關節可以支援限制(limits)和馬達(motors)。
關節限制(joint limit)
一個關節限制(joint limit)限定了一個關節的運動範圍。例如人類的胳膊肘只能做某一範圍角度的運
動。
關節馬達(joint motor)
一個關節馬達能依照關節的自由度來驅動所連線的物體。例如,你可以使用一個馬達來驅動一個肘的
旋轉。
夾具(fixture)
剛體物理資訊(如:型別、密度係數、摩擦係數)的封裝。
一個剛體可以具有多個夾具。
每一個夾具都對應一個形狀。
摩擦
摩擦可以使物件逼真地沿其它物件滑動。Box2D 支援靜摩擦和動摩擦,但使用相同的引數。摩擦在Box2D 中會被正確地模擬,並且摩擦力的強度與正交力(稱之為庫侖摩擦)成正比。摩擦引數經常會設定在 0 到 1 之間,0 意味著沒有摩擦,1 會產生強摩擦。
恢復
恢復可以使物件彈起,想象一下,在桌面上方丟下一個小球。恢復的值通常設定在 0 到 1 之間,0 的意思是小球不會彈起,這稱為非彈性碰撞;1 的意思是小球的速度會得到精確的反射,這稱為完全彈性碰撞。
密度
Box2D 可以根據附加形狀的質量分配來計算物體的質量以及轉動慣量。直接指定物體質量常常會導致不協調的模擬。因此,推薦的方法是使用 b2Body::SetMassFromShape 來根據形狀設定質量。
世界(world)
世界就是一個封閉的多邊形。一般世界的大小和螢幕的大小一樣。處於世界中的物體才會進行計算,這是為了提高效率。
世界也是物體,是形狀和約束相互作用的集合。
以上概述來自Box2D中文手冊。
在Cocos2d-x中使用Box2D
說明
開發環境說明:
引擎版本:Cocos2d-x2.2.1
開發工具:VS2012
系統:win7
工程配置
1.啟用Box2D:工程上右鍵——C/C++——前處理器——前處理器定義——編輯——新增巨集(CC_ENABLE_BOX2D_INTEGRATION)
注意:Cocos2d-x中集成了Box2D和Chipmunk,但是這兩個物理引擎不能同時使用。啟用chipmunk的巨集定義(CC_ENABLE_CHIPMUNK_INTEGRATION)
2.修改libExternsions專案的預處理指令CC_ENABLE_CHIPMUNK_INTEGRATION為CC_ENABLE_BOX2D_INTEGRATION
3.連結box2d庫(libBox2d.lib)
Cocos2d-x2.2.6的一個bug
執行TestCpp中的Box2dTest時,當新增若干個方塊後會報如下錯誤。
縮放因子
Box2D中使用米、千克、秒作單位。但是一般作畫面渲染時都是以畫素為單位的。
比如,我們在螢幕渲染一個寬為32畫素的精靈,但是在物理空間中如何表示它的寬呢?
這時,就需要定義一個縮放因子用於將畫素值轉換為物理空間的值。
把畫素/米比率設定為32是一個比較合適的值,所以定義縮放因子的值為32.
// 定義縮放因子
#define PTM_RATIO 32
建立物理世界
在CCLayer初始化的時候,初始化物理世界。
// 建立一個世界
b2Vec2 gravity(0,-10); // 設定世界的橫向和垂直重力速度,正常重力加速度約等於9.8,這裡取10是出於效率考慮
pWorld = new b2World(gravity);
// 告訴世界(world)當物體停止移動時允許物體休眠。一個休眠中的物體不需要任何模擬。
pWorld->SetAllowSleeping(true);
// 在Box2D中世界也是一個物體(剛體),所以通過建立一個剛體來定義世界的位置和大小
// 1.定義世界的位置
b2BodyDef worldDef;
worldDef.position.Set(VisibleRect::leftBottom().x,VisibleRect::leftBottom().y); //設定世界的位置在螢幕左下角
// 建立剛體,這個剛體已經被新增到世界
b2Body* groundBody = pWorld->CreateBody(&worldDef);
// 2.定義世界的邊界
// 定義邊界盒子,b2EdgeShape是通過設定一條條線段來組成一個多邊形
b2EdgeShape groundBox;
// 底邊(兩點一線)
groundBox.Set(b2Vec2(VisibleRect::leftBottom().x/PTM_RATIO,VisibleRect::leftBottom().y/PTM_RATIO), b2Vec2(VisibleRect::rightBottom().x/PTM_RATIO,VisibleRect::rightBottom().y/PTM_RATIO));
// 建立夾具(夾具是用於記錄物體資訊),實際上是把我們上面定義的線創建出來
groundBody->CreateFixture(&groundBox,0); //引數1為形狀,引數2為密度(Box2D 可以根據附加形狀的質量分配來計算物體的質量以及轉動慣量)
// 頂邊
groundBox.Set(b2Vec2(VisibleRect::leftTop().x/PTM_RATIO,VisibleRect::leftTop().y/PTM_RATIO), b2Vec2(VisibleRect::rightTop().x/PTM_RATIO,VisibleRect::rightTop().y/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);
// 左邊
groundBox.Set(b2Vec2(VisibleRect::leftTop().x/PTM_RATIO,VisibleRect::leftTop().y/PTM_RATIO), b2Vec2(VisibleRect::leftBottom().x/PTM_RATIO,VisibleRect::leftBottom().y/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);
// 右邊
groundBox.Set(b2Vec2(VisibleRect::rightBottom().x/PTM_RATIO,VisibleRect::rightBottom().y/PTM_RATIO), b2Vec2(VisibleRect::rightTop().x/PTM_RATIO,VisibleRect::rightTop().y/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);
// 迭代次數的設定
static const int velocityIterations = 8; // 速度迭代次數
static const int positionIterations = 1; // 位置迭代次數
void Box2DTest::update(float dt)
{
//更少的迭代會增加效能並降低精度,同樣地,更多的迭代會減少效能但提高模擬質量。
// 呼叫Step方法遍歷一次物理世界裡的所有物件
pWorld->Step(dt, velocityIterations, positionIterations);
}
剛體與CCSprite繫結
Cocos2d-x中已經封裝了一個用於物理引擎的CCSprite,就是CCPhysicsSprite,這個類就定義在擴充套件庫中。#include "cocos-ext.h"就可以使用了。通過setB2Body方法與剛體進行關聯。
// 1. 建立剛體
// 剛體定義
b2BodyDef bodyDef;
// 設定剛體型別為動態剛體
bodyDef.type = b2_dynamicBody;
// 設定剛體位置(注意:需要轉換為物理空間單位,除以縮放因子)
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
// 建立剛體
b2Body *body = pWorld->CreateBody(&bodyDef);
// 2.定義剛體的形狀(多邊形)
b2PolygonShape dynamicBox;
// 通過b2PolygonShape定義一個簡單的矩形
dynamicBox.SetAsBox(.5f, .5f);//設定矩形的中心點,Box2D會根據該形狀所依附的剛體的計算矩形的尺寸。
// 3.夾具定義
b2FixtureDef fixtureDef;
// 夾具形狀
fixtureDef.shape = &dynamicBox;
// 密度係數
fixtureDef.density = 1.0f;
// 摩擦係數
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
CCNode *parent = this->getChildByTag(kTagParentNode);
// 有一張64x64的精靈表,精靈表中有4個不同的32x32的圖片,隨機挑選其中一張
int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);
// 4. 剛體與精靈繫結
// 建立物理精靈
CCPhysicsSprite *sprite = CCPhysicsSprite::createWithTexture(m_pSpriteTexture,CCRectMake(32 * idx,32 * idy,32,32));
// 物理精靈與剛體關聯後,當呼叫世界的Step方法時就會更新精靈的位置,不需要手動更新
sprite->setB2Body(body);
// 設定縮放比例因子
sprite->setPTMRatio(PTM_RATIO);
// 設定精靈位置
sprite->setPosition( ccp( p.x, p.y) );//注意:一定要先呼叫setB2Body
// 新增到螢幕
parent->addChild(sprite);
除錯繪圖
b2Draw定義了用於繪製相關物理資訊的介面,只需要實現這些介面。並呼叫世界(world)的SetDebugDraw方法關聯DebugDraw。
DebugDraw在Cocos2d-x中已經有相關實現,就在cocos2d-x-2.2.1\samples\Cpp\TestCpp\Classes\Box2DTestBed目錄下的GLES-Render.cpp和GLES-Render.h。
1.把GLES-Render.cpp和GLES-Render.h拷貝到工程Classes目錄下。
2.包含標頭檔案。
#ifdef _DEBUG
#include "GLES-Render.h"
#endif
3.在建立世界的時候,建立DebugDraw,設定Debug標記並關聯到世界。並重寫CCLayer的draw方法,繪製除錯資訊。
// 繪製除錯資訊
#ifdef _DEBUG
// 建立DebugDraw,建構函式需要傳遞縮放比例因子
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
// 世界關聯DebugDraw
pWorld->SetDebugDraw(m_debugDraw);
// Debug標記
uint32 flags = 0;
flags += b2Draw::e_shapeBit; //繪製形狀
flags += b2Draw::e_jointBit; //繪製關節
flags += b2Draw::e_aabbBit; //繪製碰撞盒子
flags += b2Draw::e_pairBit; //繪製潛在接觸
flags += b2Draw::e_centerOfMassBit; //繪製質點
// 設定Debug標記
m_debugDraw->SetFlags(flags);
#endif
void Box2DTest::draw()
{
// 呼叫父類draw方法
CCLayer::draw();
#if _DEBUG
// 頂點屬性(位置、顏色、紋理座標)
ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );
kmGLPushMatrix();
// 繪製Debug資料
pWorld->DrawDebugData();
kmGLPopMatrix();
#endif
}
4.在解構函式中釋放debugDraw佔用的記憶體。
Box2DTest::~Box2DTest(){
CC_SAFE_DELETE(pWorld);
CC_SAFE_DELETE(m_debugDraw);
}
1.當物體受力的時候(碰撞、擠壓或下落),碰撞盒子的顏色會變成粉紅色,預設為青色。
2.最外邊的是剛體的形狀。
注意:建議先把縮放因子的值設定為64,然後再除錯,這樣便於觀察,因為縮放因子為32時,碰撞盒子的大小和精靈的大小一樣。