1. 程式人生 > >【Cocos2d-x】物理引擎使用入門

【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時,碰撞盒子的大小和精靈的大小一樣。