HTML5之2D物理引擎 Box2D for javascript Games 系列 第二部分


 

這是系列第二部分,之前部分在本博客中找

源碼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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM