cocos2d-x快樂的做讓人快樂的遊戲2:box2d基礎

1.cocos2d 自帶了兩套物理引擎:Box2D 和Chipmunk。 
兩套引擎都是爲2D遊戲設計的,可以和cocos2d 完美整合。 
Box2D 是用 C++寫的,而 Chipmunk 用的是 C。 
Box2D中的變量和方法名都是用全稱命名的,Chipmunk中很多地方用的是隻有一個字母的簡寫。有一些功能只有Box2D提供,Chipmunk是沒有的。比如,Box2D有針對快速移動 
物體(例如子彈)直接穿透物體而不進行碰撞測試的解決方法。 ... 

2.剛體: 
物理物體叫做“剛體”(Rigid Bodies),這是因爲物理引擎在驅動這些物體生成動畫時,把它們當作硬的,不會變形的物體。將物體這樣簡化以後,物理引擎就可以同時計算大量的剛體了。 
物理引擎中存在兩種剛體:動態移動的(dynamic )和靜態的(static)剛體。 

靜態剛體不會移動,也不應該移動- 因爲物理引擎可以依賴靜態剛體不會互相碰撞這個特性來做出一些優化。 例如:遊戲中的箱子

靜態剛體的密度可以被設爲0. 

動態剛體,它們會相互碰撞,而且也會和靜態剛體碰撞。動態剛體除了擁有位置 
(position)和旋轉(rotation )參數,還有至少3個用於定義動態剛體的參數。 
它們分別是:密度或者質量(density or mass)-  用於衡量剛體有多重。另一 
個是摩擦力(friction )-  用於表示剛體在平面上移動時遇到阻力的大小或者有多 
滑。最後是復原(restitution )-  用於定義剛體的彈性。 

3.碰撞 
現實世界裏的物體在運動時都會丟失能量,但是你可以在物理引擎中生成移動碰撞後不會丟失任何能量的動態剛體,甚至讓剛體在與別的剛體發生碰撞彈回來以後,以更快的速度進行移動。 

動態和靜態剛體外面都有一個或者多個形體。這個形體用於判斷剛體之間的碰撞的。每一次碰撞會生成一些碰撞點(contact points ) -  兩個相碰撞剛體之間的交叉點。這些碰撞點可被用於播放粒子效果或者在剛體的碰撞處動態地添加刮痕。 

物理引擎通過使用力量,脈衝和扭矩生成動態剛體的動畫。不能直接設置剛體的位置和旋轉。如果手動修改物體位置信息,物理引擎先前所作的某些假設就會失效。 

4.關節 
可以使用關節(joints )把多個剛體連接起來,這樣可以用不同的方式限制相互連接着的剛體的活動。某些關節可能配備有馬達,比如它們可被用於驅動汽車的輪子或者給關節產生摩擦力,這樣關節在向某個方向移動以後,會試着回到原來的位置。 

關節通過使用b2World類的CreateJoint方法來生成。使用剛體的 GetWorld 方法就可以得到b2World。 

// 生成旋轉關節 
b2RevoluteJointDef jointDef; 
jointDef.Initialize(bodyA, bodyB, bodyB->GetWorldCenter()); 
bodyA->GetWorld()->CreateJoint(&jointDef); 

柱狀關節:只允許朝一個方向移動,單筒望遠鏡是一個應用柱狀關節的很好的例子。 
b2PrismaticJointDef jointDef; 
b2Vec2 worldAxis(0.0f, 1.0f); 
jointDef.Initialize(bodyA, bodyB, body->GetWorldCenter(), w orldAxis); 
jointDef.lowerTranslation = 0.0f; 
jointDef.upperTranslation = 0.75f; 
jointDef.enableLimit = true; 
jointDef.maxMoto rForce = 60.0f; 
jointDef.motorSpeed = 20.0f; 
jointDef.enableMotor = false; 
joint = (b2PrismaticJoint*)body- >GetWorld()->CreateJoint(&jointDef); 


把活塞的摩擦力和密度設置爲極限值,這樣彈球就不會在碰到活塞的時候彈跳出去了,這可以保證平滑的發射動作。 
fixtureDef.friction = 0.99f; 
fixtureDef.restitution = 0.01f; 

5.物理引擎的侷限性 
真實世界過於複雜,完全放到物理引擎中進行模擬是不可能的。這就是爲什麼要使用剛體的原因。 
在某些極端情況下,物理引擎有可能會捕捉不到某些已經發生的碰撞 – 例如,當剛體以很快的速度移動時,一個剛體可能直接穿透另一個剛體。雖然在量子物理學中這樣的穿透情況會發生。 

剛體有時候會相互穿透卡在一起,特別是在使用了關節將它們連接在一起以後。卡在一起的剛體會努力要分開,但是爲了滿足關節的連接要求,它們又不得不卡在一起,結果是卡在一起的剛體會產生顫動。 

我們也可能碰到遊戲運行的問題。如果我們在遊戲裏使用了很多剛體,你永遠不會知道這些剛體相互作用後的最終結果。最終,有些玩家會把自己卡死在剛體中,或者他們也可能會發現如何利用物理模擬的漏洞,跑到遊戲中他們本來不應該去的區域。 

6.Box2D 
因爲Box2D使用C++寫的,所以必須使用.mm作爲所有項目的實現文件的後綴名,而不是通常的.m後綴名。.mm後綴用於告知Xcode把有此後綴名的文件作爲Objective-C++或者C++代碼來處理。如果你使用了.m後綴,Xcode就會把代碼以Objective-C和C來處理,Xcode就不能正確處理Box2D的C++代碼了。因此,如果碰到很多編譯錯誤,首先檢查一下是不是所有的實現文件都是以.mm作爲後綴的。 

Box2D手冊:http://www.box2d.org/manual.html 
Box2D的API參考下載:http://code.google.com/p/box2d 

7.b2World 
b2World初始化時使用了一個初始的重力矢量值和用於決定是否允許動態剛體“睡眠”的標記變量。 
例:對Box2D世界進行初始化 
b2Vec2 gravity = b2Vec2(0.0f, -10.0f); 
bool allowBodiesToSleep = true; 
world = new b2World(gravity, allowBodiesToSleep); 

會“睡眠”的動態剛體:當施加到某個剛體上的力量小於臨界值一段時間以後,這個剛體將會進入“睡眠”狀態。換句話說,如果某個剛體移動或者旋轉的很慢或者根本不在動的話,物理引擎將會把它標記爲“睡眠”狀態,不再對其施加力量,直到新的力量施加到剛體 
上讓其再次移動或者旋轉。通過把一些剛體標記爲“睡眠”狀態,物理引擎可 
以省下很多時間。除非你遊戲中的所有動態剛體處於持續的運動中,否則應該把 
allowBodiesToSleep變量設置爲true。 

傳入Box2D世界的重力是一個b2Vec2的struct類型。它和CGPoint在本質上是 
一樣的,都儲存着x 軸和y 軸的浮點值。和真實世界一樣重力都是一個常量(0,-10)。 

8.剛體b2Body 
把活動範圍限制在屏幕之內: 
創建一個靜態剛體: 
b2BodyDef containerBodyDef; 
b2Body* containerBody = world->CreateBody(&containerBodyDef); 
剛體通常都是使用world的CreateBody方法來生成的,可以確保正確地分配和釋放剛體所佔用的內存。默認情況下,一個空的剛體定義會生成一個位於(0,0)位置的靜態剛體。 

填充4條邊,以構成空心剛體,屏幕的四個邊需要單獨創建,以便只有四個邊是實心的,中間則是空心的,用於放入其它剛體: 
b2PolygonShape screenBoxShape; 
int density = 0; 
// 底部 
screenBoxShape.SetAsEdge(lowerLeftCorner, lowerRightCorner); 
containerBody->CreateFixture(&screenBoxShape, density); 
// 頂部 
screenBoxShape.SetAsEdge(upperLe ftCorner, upperRightCorner); 
containerBody->CreateFixture(&screenBoxShape, density); 
// 左邊 
screenBoxShape.SetAsEdge(upperLeftCorner, lowerLeftCorner); 
containerBody->CreateFixture(&screenBoxShape, density); 
// 右邊 
screenBoxShape.SetAsEdge(upperRi ghtCorner, lowerRightCorner); 
containerBody->CreateFixture(&screenBoxShape, density); 

b2PolygonShape類還有SetAsBox方法,調用它會生成的是一個實心的剛體。 
使用SetAsBox方法生成的盒子是其接受的參數大小的兩倍,所以提供給這個方法的 
參數座標值要除以2,或者乘以0.5f。 

9.單位:Box2D中距離以米爲單位,質量以公斤爲單位,時間以秒爲單位。 
Box2D在0.1米到10米的範圍內工作是最優化的,因爲它針對這個範圍做過專門優化,把創建的Box2D世界中的剛體的大小限定在越接近1米越好,太小或者太大的剛體很可能會在遊戲運行過程中產生錯誤和奇怪的行爲。 
PTM_RATIO的定義如下: #define PTM_RATIO 32 
PTM_RATIO用於定義32個像素在Box2D世界中等同於1米。一個有32像素寬和高的 
盒子形狀的剛體等同於1米寬和高的物體。2x32像素大小的瓷磚的尺寸剛好是1x1米,大小4x4像素的大小是0.125米x0.125米。 
Box2d在處理大小在0.1到10個單元的對象的時候做了一些優化。這裏的0.1米大概就是一個杯子那麼大,10的話,大概就是一個箱子的大小。 

屏幕的寬度和高度值除以一個名爲 PTM_RATIO的常量,把像素值轉換成了以米爲單位來 
計算長度: 
CGSize screenSize = [CCDirector sharedDirector].winSize; 
float widthInMeters = screenSize.width / PTM_RATIO; 
float heightInMeters = screenSize.height / PTM_RATIO; 
//四個角的座標, 以米爲單位: 
b2Vec2 lowerLeftCorner = b2Vec2(0, 0); 
b2Vec2 lowerRightCorner = b2Vec2(widthInMeters, 0); 
b2Vec2 upperLeftCorner = b2Vec2(0, heightInMeters); 
b2Vec2 upperRightCorner = b2Vec2(widthInMeters, heightInMeters) ; 

在b2Vec2和CGPoint之間轉換: 
-(b2Vec2) toMeters:(CGPoint)point 

return b2Vec2(point.x / PTM_R ATIO, point.y / PTM_RATIO); 

-(CGPoint) toPixels:(b2Vec2)vec 

return ccpMult(CGPointMake (vec.x, vec.y), PTM_RATIO); 


10.生成一個由精靈作爲表現的動態剛體示例: 
-(void) addNewSpriteAt:(CGPoint)pos  

CCSpriteBatchNode* batch = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode]; 
int idx = CCRANDOM_0_1() * TILESET_COLUMNS; 
int idy = CCRANDOM_0_1 () * TILESET_ROWS; 
CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE); 
CCSprite* sprite = [CCSprite sp riteWithBatchNod e:batch rect:tileRect]; 
sprite.position = pos; 
[batch addChild:sprite]; 

//創建一個剛體的定義,並將其設置爲動態剛體 
b2BodyDef  bodyDef; 
bodyDef.type = b2_dynamicBody; 
bodyDef.position = [self toMeters:pos]; 
bodyDef.userData = sprite; 
b2Body* body = world->CreateBody(&bodyDef); 

// 定義一個盒子形狀,並將其複製給body fixture 
b2PolygonShape  dynamicBox; 
float tileInMeters = TILESIZE / PTM_RATIO; 
dynamicBox.SetAsBox(tileInMeters  * 0.5f, tileInMe ters * 0.5f); 

b2FixtureDef fixtureDef; 
fixtureDef.shape = &dynamicBox; 
fixtureDef.density = 0.3f; 
fixtureDef.friction = 0.5f; 
fixtureDef.restitution = 0.6f; 
body->CreateFixture(&fixtureDef); 


世界創建剛體,剛體再創建它的附着物。剛體和精靈沒有直接關係,它們只是位置被維持相同罷了。 
可以把b2FixtureDef 理解成包含着剛體需要用到的所有數據的容器。這些數據包括:剛體的形狀(最重要的一項),密度,摩擦力和復原(這項會影響剛體在world中移動和彈跳的方式)。 

11.box2d的動畫 
剛體看不見但摸的着,精靈看的見但摸不着。在update方法中遍歷每個剛體,把剛體的userData返回,並且轉換成CCSprite指針。把剛體的位置信息轉換成像素值,賦值給精靈的位置屬性,讓精靈可以隨着剛體一起移動。同樣地,設置精靈的角度值。 

Box2D的world是通過定期地調用Step方法來實現動畫的。 
Step方法需要三個參數。 
第一個是timeStep,它會告訴Box2D自從上次更新以後已經過去多長時間了,直接影響着剛體會在這一步移動多長距離。不建議使用delta time來作爲timeStep的值,因爲delta time會上下浮動,剛體就不能以相同的速度移動了。
第二和第三個參數是迭代次數。它們被用於決定物理模擬的精確程度,也決定着計算剛體移動所需要的時間。 

示例:更新每個剛體相關聯的精靈的位置和旋轉信息 
-(void) update:(ccTime)delta 

//使用固定的時間間隔將物理模擬向前推進一步 
float timeStep = 0.03f; 
int32 velocityIterations = 8; 
int32 positionIterations = 1; 
world->Step(timeStep, velocityIterati ons, positionIterations); 

for (b2Body* body = world->GetBodyList(); body != nil; body = body->GetNext()) 

CCSprite* sprite = (CCSprite*)body->GetUserData(); 
if (sprite!= NULL) 

sprite.position = [self toPixels:body->GetPosition()]; 
float angle = body ->GetAngle(); 
sprite.rotation = CC_RADIAN S_TO_DEGREES(angle) * -1; 




12.碰撞測試 
創建一個繼承自b2ContactListener的新類,重寫BeginContact和EndContact這兩個方法,任何時候兩個剛體發生碰撞時都會調用這兩個方法。 

將它的實例設爲world的contact listener: 
contactListener = new ContactListener(); 
world->SetContactListener(contactListener); 

在b2ContactListener實例的回調中不能進行任何更改遊戲物理世界的操作,所以我們可以保存兩個碰撞物體的引用,然後待會在update中做其它處理。 


13. 
凸面體的特點是:在凸面體裏面找任意兩個點,這兩個點連成的線的任何部分都不會落在凸面體外面。 
凹面體(Concave)裏任意兩個點連成的線可能有一部分會落在外面。 
組成多邊形的頂點在定義是要以反時針方向來進行。 
使用VertexHelper工具,通過把頂點一個接一個畫出來的方式來 生成碰撞測試用的多邊形。 
VertexHelper的源代碼是通過GitHub來共享的: http://github.com/jfahrenkrug/VertexHelper.,下載下來是一個xcode mac工程, 

VertexHelper只是一個幫助尋找凸面體頂點數組的工具,我們需要的是它生成的多邊形頂點數組,對於一個物體,可以把它當作一個凸面體,而對於一個地圖,要在其中找到多個凸面體,在VertexHelper中生成的頂點數組代碼,可以一段一段的拷貝,當作多個多邊形,只要認爲某一段的頂點若連起來後是凸面體就好了。 

頂點的位置是相對於body中心的,最大頂點數量不能超過8,數量越大,越費內存,性能也越差。 


14.步驟 
加載場景,場景中有一個CCLayer 
在CCLayer中初始化世界:設置邊界,設置監聽 
啓用渲染調試 
加載紋理貼圖,*.plist 
在CCLayer中加載背景顏色層 CCLayerColor 
在CCLayer中添加精靈批處理,CCSpriteBatchNode,(可以在每個CCNode中添加同一個紋理貼圖生成的精靈批處理,以方便此node中快速生成sprite) 
添加靜態元素層CCNode,此node中可以添加一些CCSprite。 
使用world創建剛體,然後創建和剛體對應的sprite添加到精靈批處理,並關聯給剛體的userdata,此處代碼也可以封裝一下。 
在預約的update回調方法中,遍歷世界中的所有剛體,把精靈的位置和剛體的位置及角度同步。 
示例中的BodyNode被添加到了TableSetup中,只是起到了保存實例引用的作用。 


15.形狀 
b2CircleShape shape; 
float radiusInMeters =  (tempSprite.contentS ize.width / PTM_RATI O) * 0.5f; 
shape.m_radius = radiusInMeters; 

將b2BodyDef的angularDamping的域設爲0.9f,它會讓球對轉彎的動作產生更大的阻力。這樣彈球在滑過彈球桌面的時候不會產生很多的自身旋轉,這對於用金屬製作的有一定重量的彈球來說是標準的移動方式。 


16.variable length array of non-POD element type 'b2Vec2' 

問題分析:錯誤指向的是cocos2d裏一個結構體數組定義,如下: 

b2Vec2 vertices[vertexCount]; 

問題解決:把其改成指針形式,如下: 

b2Vec2 *vertices = new b2Vec2[vertexCount]; 

搞定。 

17.添加box2d 
將libs/box2d目錄拷貝到工程中; 
User Header Search Paths   "KnowledgeGame/Classes/libs"     non 
Always search user paths  YES 
所有與box2d扯上關係的源文件修改爲.mm 

18.Box2D具體是如何運作的。 
  創建了world對象,接下來需要往裏面加入一些body對象。body對象可以隨意移動,可以是怪物或者飛鏢什麼的,只要是參與碰撞的遊戲對象都要爲之創建一個相應的body對象。當然,也可以創建一些靜態的body對象,用來表示遊戲中的臺階或者牆壁等不可以移動的物體。 
  爲了創建一個body對象,首先,創建一個body定義結構,然後是body對象,再指定一個shap,再是fixture定義,然後再創建一個fixture對象。下面詳細介紹這個過程: 
    首先創建一個body定義結構體,用以指定body的初始屬性,比如位置或者速度。 
    然後調用world對象來創建一個body對象。 
    然後爲body對象定義一個shape,用以指定想要仿真的物體的幾何形狀。 
    接着創建一個fixture定義,同時設置之前創建好的shape爲fixture的一個屬性,並且設置其它的屬性,比如質量或者摩擦力。 
    最後,可以使用body對象來創建fixture對象,通過傳入一個fixture的定義結構就可以了。 
    請注意,可以往單個body對象裏面添加很多個fixture對象。這個功能在創建特別複雜的對象的時候非常有用。比如自行車,可能要創建2個輪子,車身等等,這些fixture可以用關節連接起來。 

只要把所有需要創建的body對象都創建好之後,box2d接下來就會接管工作,並且高效地進行物理仿真—只要你週期性地調用world對象的step函數就可以了。 
  但是,請注意,box2d僅僅是更新它內部模型對象的位置–如果想讓cocos2d裏面的sprite的位置也更新,並且和物理仿真中的位置相同的話,那麼你也需要週期性地更新精靈的位置。 

19.density,friction和restitution參數的意義。 
    Density 就是單位體積的質量(密度)。因此,一個對象的密度越大,那麼它就有更多的質量,當然就會越難以移動. 
    Friction 就是摩擦力。它的範圍是0-1.0, 0意味着沒有摩擦,1代表最大摩擦,幾乎移不動的摩擦。 
    Restitution 回覆力。它的範圍也是0到1.0. 0意味着對象碰撞之後不會反彈,1意味着是完全彈性碰撞,會以同樣的速度反彈。 


20.Box2d用戶手冊 

http://www.box2d.org/manual.html 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章