這是系列第二部分,之前部分在本博客中找
源碼demo存放在https://github.com/willian12345/Box2D-for-Javascript-Games
向世界添加剛體
剛體(Bodies)是我們用Box2D創建物理游戲的重要對象。任何你可以移動的或交互 的對象都是剛體(Bodies)。
憤怒的小鳥(Angry Birds)中創建的小鳥和小豬是剛 體,同樣在圖騰破壞者(Totem Destroyer)中的黃金神像和圖騰磚塊也是剛體。
本章將帶你學習創建各種類型的Box2D剛體,此外還有一些其它重要的特性,如下表所列
• 創建圓形剛體
• 創建矩形剛體
• 創建任意多邊形剛體
• 使用DebugDraw()方法測試模擬
• 定義剛體的類型:static,dynamic或kinimatic
• 設置材質屬性:密度(density),摩擦系數(friction)和恢復系數(resitution)
• 度量單位
• 創建合成對象
通過本章的學習,你將會創建一個你的第一個圖騰破壞者類型的游戲。本章有較
多的知識點,那么我們廢話少說,直接開始本章的學習吧!
你的第一個模擬—一個球落地
我們先從簡單的任務開始,最簡單的物理模擬:一個球落到地面。總之,雖然
這是一個簡單小球落地的模擬,但是它將是你的第一個模擬,並且易於很快實
現它。
讓我們看看在這次模擬中我們要做些什么:
• 世界的重力(gravity)
• 一個受到作用力(例如:重力(gravity))的球
• 一個不受任何作用力的地面
• 某種材質,正如我們希望小球在地面彈起的材質
在之前的學習中,你已經能夠配置世界的重力了,所以我們從創建小球開始本章的代
碼編寫。
1. 無論我們是創建球形還是多邊形,第一步都是創建一個剛體:
var bodyDef =new b2BodyDef();
b2BodyDef類是一個剛體的定義類,它將持有創建我們剛體所需要的所有數 據。
2. 現在可以將剛體添加到世界中。因為我們采用的舞台尺寸是640X480,我們將 把球放置在舞台的頂部的中心位置,該位置為(320,30),如下所示:
bodyDef.position.Set(10.66,1);
通過position屬性顯示的設置了剛體在世界中的位置,但是我確信你會對我之前 所說的位置為(320,30)的設置而變成(10.66,1)而感到困惑。
這原因要關系 到度量單位上。雖然Flash是以像素(pixels)為度量單位,但是在Box2D中嘗試 模擬真實的世界並采用米(meters)作為度量單位。
對於米(meters)和像素 (pixels)之間的轉換沒有通用的標准,但是我們采用下面的轉換標准可以有很 好的運行效果:
1米 = 30像素
所以,如果我們定義一個變量來幫助我們將米(meters)轉換成像素 (pixels),我們便可以在Box2D世界(world)中進行操作時使用像素 (pixels)而不用使用米(meters)來作為度量單位。
這樣將使我們在制作 Flash游戲時,使用像素來思考,從而變得更加直觀。
3. 打開你在第一章中創建的demo1-1.html,並像下面那樣修改它:
<script> function init(){ var b2Vec2 = Box2D.Common.Math.b2Vec2 ,b2AABB = Box2D.Collision.b2AABB ,b2BodyDef = Box2D.Dynamics.b2BodyDef ,b2Body = Box2D.Dynamics.b2Body ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef ,b2Fixture = Box2D.Dynamics.b2Fixture ,b2World = Box2D.Dynamics.b2World ,b2MassData = Box2D.Collision.Shapes.b2MassData ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw ,b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef ; var world; var worldScale = 30; function main(){ world = new b2World(new b2Vec2(0, 9.81), true); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320/worldScale,30/worldScale); setInterval(updateWorld, 1000 / 60); } function updateWorld() { world.Step(1/30,10,10); world.ClearForces(); // 清除作用力 } main(); } init(); </script>
並且,注意我是怎樣創建世界和調用step方法的。這比之前少用了幾行代碼。
一旦你創建了剛體定義,那么是時候給它一個形狀了。
創建一個圓形形狀
形狀(shape)是一個2D幾何對象,例如一個圓形或者多邊形,在這里必須是凸多邊
形(每一個內角小於180度)。記住,Box2D只能處理凸多邊形
現在,我們從小球開始,所以我們創建一個圓形:
var circleShape =new b2CircleShape(25/worldScale);
b2CircleShape是用來創建圓形形狀,並且它的構造函數需要一個半徑(radius)作為 參數。
在之前的代碼中,我們創建了一個圓形,它的半徑為25像素(pixels),由於設 置了worldScale變量。
從現在起,每次你想要使用像素進行操作時,你只要將它們除以 worldScale即可。你也可以定義一個方法名為pixelsToMeters的方法,在每次你需要將像 素(pixels)轉換成米(meters)時調用。
當我們有了剛體定義和形狀時,我們將使用夾具(fixture)來將它們粘合起來。
創建夾具
夾具(fixture)用於將形狀綁定到剛體上,然后定義它的材質,設置密度 (density),摩擦系數(friction)以及恢復系數(restitution)。
此刻我們無需去 擔心材質,讓我們把注意力集中到夾具(fixture)上:
1.首先,我們創建夾具(fixture):
var fixtureDef = new b2FixtureDef(); fixtureDef.shape=circleShape;
一旦我們通過構造函數創建了夾具(fixture),我們將分配之前創建 的形狀給它的shape屬性。
2.最后,我們准備將球添加到世界中:
var theBall =world.CreateBody(bodyDef); theBall.CreateFixture(fixtureDef);
b2Body是剛體的實體:是物質,是通過使用bodyDef屬性創建的具 體剛體。
3.再次說明一下,使用以下步驟將剛體添加到世界中:
I 創建一個剛體定義,它將持有剛體信息,例如剛體的位置信息。
II 創建一個形狀,它將決定剛體的顯示形狀
III. 創建一個夾具,將形狀附加到剛體定義上。
IV. 創建剛體在世界中的實體,使用夾具。
一旦你知道了每一步的重要性,添加剛體到你的Box2D世界中將會 很容易和有趣
回到我們的項目。現在的main函數內應該看起來和下面一樣:
function main(){ world = new b2World(new b2Vec2(0, 9.81), true); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320/worldScale,30/worldScale); var circleShape = new b2CircleShape(25/worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = circleShape; fixtureDef.density = 1; fixtureDef.restitution = .6; fixtureDef.friction = .1; var theBall = world.CreateBody(bodyDef); theBall.CreateFixture(fixtureDef); setInterval(updateWorld, 1000 / 60); }
定時保存項目並測試它。准備好看看你的第一個Box2D剛體的活動?運行影片!
額…,然而你現在運行時還是看不到任何東西。。讓我告訴你原因,Box2D只負責模擬物理世界,而不負責顯示任何東西。
這意味着,你的剛體正活躍在你的Box2D世界中,只是你看不到而已。
使用調試繪圖測試你的模擬
幸運的是,Box2D有一個特性,調試繪圖(debug draw),它將幫助你顯示出模擬的情況:
在網頁中首先要添加一個canvas如
<canvas id="canvas" width="640" height="480" style="" ></canvas>
1.調試繪圖(debug draw)將Box2D世界中發生的事情顯示出來,在
updateWorld方法中,我們可以在Step()方法之后調用世界(world)的 DrawDebugData()方法:
world.DrawDebugData();
2. 一旦我們告知世界在每次遍歷之后顯示調試繪圖(debug draw),我們需要通 過調試繪圖(debug draw)定義視覺設置。如下添加代碼到你的main函數內:
var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(document.getElementById("canvas").getContext("2d")); debugDraw.SetDrawScale(worldScale); debugDraw.SetFillAlpha(0.5); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw);
3.這里有很多代碼,所以讓我們來解釋一下發生了什么。你已經知道 DrawDebugData()方法代表什么,所以我們將解釋其它行代碼代表的意思:
var debugDraw = new b2DebugDraw();
b2DebugDraw是一個類,它支持調試繪圖(debug draw)出你的游戲中的物理 實體。
var debugSprite:Sprite = new Sprite();
debugSprite被添加到顯示列表(Display List),准備顯示在canvas上。
debugDraw.SetSprite(debugSprite);
SetSprite()方法告知debugSprite將要被用來顯示調試繪圖 (debug draw)。
debugDraw.SetDrawScale(worldScale);
因為我們要將米(meters)轉變為像素(pixels),我們需要通知調試繪 圖(debug draw)我們使用的換算比例。 debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
SetFlag()方法允許我們決定我們將在調試繪圖(debug draw)中描繪的物 理實體的類型。此刻,我們只需要繪制形狀。
補充說明:
setFlag()方法選擇性的繪制Box2D對象的內容。這樣可以節省CPU開支。setFlag()方法有一個16進制的參數,這參數的取值只能是b2DebugDraw中定義的下面幾個常量
另外,我們還可以用”或”運算符,同時使用多個Flag
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
debugDraw.SetFillAlpha(0.5);
SetFillAlpha()方法是為了便於觀看而設置的。形狀的輪廓是不透明的,填 充的顏色是半透明的。這將使得調試繪圖輸出更加易於理解。
world.SetDebugDraw(debugDraw);
最后,我們將指派調試繪圖(Debug draw)到我們剛剛創建的世界(world)
4.現在是時候來測試一下你影片了,然后你應該會看到下圖所示的樣子:
就這樣!你設法看到了你放置在Box2D世界中的剛體。
目前,球體還無法在重力的作用下下落,但是不要擔心,我們將在稍后修改它。
現在,讓我們來創建一些可以作為地面的東西,例如一個放置在舞台底部邊緣的大矩
形。從現在開始一切將更加簡單,作為新的剛體將會很快的自動顯示在它所添加的世界中。
完整源碼在demo2-1.html中查看
創建矩形形狀
讓我執行下面的步驟:
1.首先,剛體和夾具的定義可以重指定到我們定義的新的剛體上。這樣,我 們無需再去定義bodyDef變量,但是我們要改變原先在創建球時使用的坐 標:
bodyDef.position.Set(320/worldScale,470/worldScale);
2.我們將用b2PolygonShape類創建一個多邊形:
var polygonShape = new b2PolygonShape();
這樣,我們以之前創建圓形形狀時,相同的方法創建了一個多邊形形狀。
3.多邊形形狀必須遵守一些限制,但是目前,因為我們只需要一個軸對稱的矩 形,SetAsBox()方法便能滿足我們的需要:
polygonShape.SetAsBox(320/worldScale,10/worldScale);
這個方法需要兩個參數:矩形的半寬長和半高長。最后,我們的新多邊形形狀 的中心在像素(320,470),它的寬度為640像素和高度為20像素——這是我們 剛剛創建的地面的尺寸。
4.現在,我們改變定義的夾具的shape屬性,附加新的多邊形形狀:
fixtureDef.shape = polygonShape;
5.最后,我們可以創建剛體並將夾具附加上去,就像我們在球形上做的那樣。
var theFloor = world.CreateBody(bodyDef); theFloor.CreateFixture(fixtureDef);
6.你的main方法應該向下面這樣:
function main(){ world = new b2World(new b2Vec2(0, 9.81), true); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320/worldScale,30/worldScale); var circleShape = new b2CircleShape(25/worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = circleShape; fixtureDef.density = 1; fixtureDef.restitution = .6; fixtureDef.friction = .1; var theBall = world.CreateBody(bodyDef); theBall.CreateFixture(fixtureDef); // 定義矩形地面 bodyDef.position.Set(320/worldScale, 470/worldScale); bodyDef.type = b2Body.b2_staticBody; var polygonShape = new b2PolygonShape(); polygonShape.SetAsBox(320/worldScale, 10/worldScale); fixtureDef.shape = polygonShape; // 復用夾具 var theFloor = world.CreateBody(bodyDef); theFloor.CreateFixture(fixtureDef); //setup debug draw var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(document.getElementById("canvas").getContext("2d")); debugDraw.SetDrawScale(worldScale); debugDraw.SetFillAlpha(0.5); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw); setInterval(updateWorld, 1000 / 60); }
7.測試影片,你將會看到地面:
完整源碼在demo2-2.html中查看
你看是不是很簡單?我們花了將近一章半去防止我們的第一個剛體,然后只花了很
少的幾行代碼添加另一個剛體。
不同的剛體類型——static,dynamic和 kinematic
有三種Box2D剛體的類型:staitc,dynamic和kinematic。
一個static類型的剛體不受任何力,沖量或撞擊的影響並且不會移動。它只能通過 用戶手動移動。默認情況下,所有的Box2D剛體都是static類型的剛體,這就是為什 么球不移動的原因。一個static類型的剛體不會和別的static或kinematic類型的剛體發 生碰撞。
一個dynamic類型的剛體受力,沖量,撞擊以及任何世界事件的影響。它可以通過 手動移動,雖然我建議讓它們通過世界的重力,和任何類型剛體的碰撞來移動。
一個kinematic類型的剛體是一個介於static和dynamic剛體之間的混合剛體。它不 受理的影響,但是可以通過手動和設置它們的速率來移動。它不能和static和 kinematic類型的剛體碰撞。
現在回到我們的 模擬鍾來。那種類型是我們要指派給球和地面的呢?
地面是static類型的剛體,它無需移動,然而通過世界重力球要移動,所以是 dynamic類型的剛體。
你只需要設置剛體定義的type屬性就能告知Box2D每一個剛體的類型,屬性值可以是
b2Body.b2_staticBody, b2Body.b2_dynamicBody或b2Body.b2_kinematicBody分別對應 static,dynamic或kinematic剛體。
為球添加上bodyDef.type=b2Body.b2_dynamicBody;
為地面添加上bodyDef.type=b2Body.b2_staticBody;
你的新main方法向下面這樣:
function main(){ world = new b2World(new b2Vec2(0, 9.81), true); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320/worldScale,30/worldScale); bodyDef.type = b2Body.b2_dynamicBody; var circleShape = new b2CircleShape(25/worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = circleShape; var theBall = world.CreateBody(bodyDef); theBall.CreateFixture(fixtureDef); // 定義矩形地面 bodyDef.position.Set(320/worldScale, 470/worldScale); // 復用定義剛體 bodyDef.type = b2Body.b2_staticBody; var polygonShape = new b2PolygonShape(); polygonShape.SetAsBox(320/worldScale, 10/worldScale); fixtureDef.shape = polygonShape; // 復用夾具 var theFloor = world.CreateBody(bodyDef); theFloor.CreateFixture(fixtureDef); //setup debug draw var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(document.getElementById("canvas").getContext("2d")); debugDraw.SetDrawScale(worldScale); debugDraw.SetFillAlpha(0.5); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw); setInterval(updateWorld, 1000 / 60); }
在恭喜你運行成功你的第一個模擬之前,讓我們花點時間來說一下關於當使用調試 繪圖(debug draw)時的不同顏色。
static類型的剛體將會繪制成綠色。dynamic類型的剛體,當它們沒有在睡眠狀態 時將會繪制成紅色,在睡眠狀態時將會繪制成灰色。
kinematic類型的剛體,在之 前的屏幕截圖中沒有顯示,它將會被顯示為藍色。
現在,我們知道當剛體進入睡眠狀態並節約CPU資源這個概念。正如你所見, 當球撞擊地面,沒有別的里影響它時,所以求可以進入睡眠狀態,知道有什么 發生為止。
現在,有一個新的問題。球在落地后沒有彈起。如果我們想要運行一個完美的模
擬,我們需要給我們的剛體一些更多的屬性。
密度,摩擦和恢復
正如你已經知道怎樣向世界添加剛體,那么我想向你介紹三種將會改變剛體行為
的屬性:密度,摩擦和恢復。
密度(density)用來設置剛體的質量,按照公斤沒平方米。越高的密度意味着越 重的剛體,並且該值不能為負。
摩擦(friction)在兩個剛體在彼此的表面上移動時產生,它是通過一個系數來定 義的,通常它的范圍在0(沒有摩擦)-1(最大摩擦)之間。它不能為負數。
恢復(restitution)決定剛體在發生碰撞時反彈的程度。與密度(density)和摩擦 (friction)一樣,它不能為負數並且它是一個介於0-1的系數來定義的。
一個小球 在恢復為0時落向地面,不發生反彈(無彈性碰撞),反之恢復為1時小球將會以此刻撞擊時相同的速率彈起(完全彈性碰撞)。
密度(density),摩擦(friction)和恢復(restitution)必須添加到夾具上,所以在main方法中添加以下幾行代碼:
fixtureDef.density=1;
fixtureDef.restitution=0.6;
fixtureDef.friction=0.1;
現在你的main函數內看起來應該是這樣的
function main(){ world = new b2World(new b2Vec2(0, 9.81), true); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320/worldScale,30/worldScale); bodyDef.type = b2Body.b2_dynamicBody; var circleShape = new b2CircleShape(25/worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = circleShape; fixtureDef.density = 1; fixtureDef.restitution = .6; fixtureDef.friction = .1; var theBall = world.CreateBody(bodyDef); theBall.CreateFixture(fixtureDef); // 定義矩形地面 bodyDef.position.Set(320/worldScale, 470/worldScale); // 復用定義剛體 bodyDef.type = b2Body.b2_staticBody; var polygonShape = new b2PolygonShape(); polygonShape.SetAsBox(320/worldScale, 10/worldScale); fixtureDef.shape = polygonShape; // 復用夾具 var theFloor = world.CreateBody(bodyDef); theFloor.CreateFixture(fixtureDef); //setup debug draw var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(document.getElementById("canvas").getContext("2d")); debugDraw.SetDrawScale(worldScale); debugDraw.SetFillAlpha(0.5); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw); setInterval(updateWorld, 1000 / 60); }
我向夾具指派一次屬性,而所有的剛體都將使用這個相同的夾具。在本書的整個
講解過程中,我們將要處理很多夾具的屬性,但是目前讓我們只需要設置小球彈跳即可。
測試demo2-3.html,你就會發現小球在彈跳
祝賀你!你剛剛完成了你的第一個真實的Box2D項目,那么現在你有能力去創建 基礎的形狀和為它們分配特性和屬性。
接下去讓我開始來創建一個准游戲吧…
注:轉載請注明出處博客園:sheldon-二狗-偷飯貓(willian12345@126.com)
https://github.com/willian12345