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

池中物willian發表於2016-05-16

 

這是系列第二部分,之前部分在本部落格中找

原始碼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

 

相關文章