2D物理引擎 Box2D for javascript Games 第七章 子彈和感應器
你知道 Box2D 可以在每一個時間步中管理剛體間的碰撞並決算它們。
總之,在憤怒的小鳥中製作攻城機器期間,發生了一些錯誤
你可能需要注意一下,有時拋射物會穿過城堡,忽略了碰撞。
這裡發生了什麼?
通常,Javascript 遊戲執行在 30 與 60 幀每秒之間,如果我們使世界的時間步與幀率同步,每一個時間步將代表 1/30 到 1/60 秒。
依賴時間步的模擬叫作離散模擬,這不同於真實的世界,在真實世界中事件的發生的是連續的,我們稱之為連續模擬
離散型模擬中,我們無法知道在時間中的第n步與第(n+1)步之間發生了什麼
如果一個剛體的移動真的很快,那麼他可以在小於一個時間步的時間之內穿過另一個剛體,你會發現它在穿過的剛體的 另一邊了,而沒有發生碰撞。
這個現象叫作隧道效應 (tunneling),並且很自然的,我們想要阻止它的發生
在本章,你將學習兩種不同的方式去管理剛體間的接觸:
- 設定剛體為子彈
- 設定剛體為感應器
透過本章的學習,你將不會再對管理高速移動的剛體有任何問題
感受隧道效應
Box2d 預設的情況下對阻止隧道效應做的很好,使用一個連續的碰撞檢測來計算離散型模擬。
不幸的是,處於效能相關的考慮,這種型別的碰撞檢測只應用在 dynamic 型別剛體與 static 型別剛體之間的碰撞上。
這意味著,我們可以在兩個 dynamic 型別的剛體之間產生隧道效應。
-
讓我們來看一下,下面的指令碼:
const stage = document.querySelector('#canvas'); function main() { world = new b2World(gravity, sleep); debugDraw(); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320 / worldScale, 470 / worldScale); var polygonShape = new b2PolygonShape(); polygonShape.SetAsBox(320 / worldScale, 10 / worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = polygonShape; fixtureDef.density = 1; fixtureDef.restitution = 0.5; fixtureDef.friction = 0.5; var body = world.CreateBody(bodyDef); body.CreateFixture(fixtureDef); bodyDef.position.Set(600 / worldScale, 240 / worldScale); bodyDef.type = b2Body.b2_dynamicBody; polygonShape.SetAsBox(10 / worldScale, 220 / worldScale); var body2 = world.CreateBody(bodyDef); body2.CreateFixture(fixtureDef); bodyDef.position.Set(320 / worldScale, 455 / worldScale); polygonShape.SetAsBox(5 / worldScale, 5 / worldScale); var body3 = world.CreateBody(bodyDef); body3.CreateFixture(fixtureDef); body3.SetLinearVelocity(new b2Vec2(100, -10)); stage.addEventListener('click', updateWorld); }
這個指令碼中沒有什麼新的知識。
它放置了三個剛體,分別叫作 body,body2,body3,它們分別代表了一個 static 型別的地面,一個 dynamic 型別的障礙物,以及一個 dynamic 型別的小子彈。
如你所見,子彈以一個非常快的速度(100,-10)發射,然後 updateWorld() 方法將執行世界的時間步,它不是在每一幀被呼叫,而是在每一次的滑鼠點選時被呼叫。
這樣可以使我們一步一步的來執行模擬,如你想要的一樣慢,並且我們可以看到發生了什麼。
-
測試網頁,然後多次點選滑鼠。
原始碼:
article/ch07/ch07-1.html
發生了什麼?拋物塊在沒有接觸到障礙物的情況下穿過了它。
我們剛剛體驗了隧道效應。
現在,讓我們做些什麼來阻止它的發生吧!
阻止隧道效應——設定剛體為子彈
因為連續的碰撞檢測使得隧道效應無法在 static 型別的剛體上發生,
在某些情況下,我們也可以將這種方法應用到 dynamic 型別的剛體上,透過設定它們為子彈(bullets)。
一個子彈(bullets)執行連續的碰撞檢測,來檢測它與 static 型別的和 dynamic 型別的剛體之間的碰撞。
記住,如果你將所有的剛體都設定為子彈(bullets),你將會發現很高的效能消耗,所以這將由你在效能和精確度之間找到一個平衡點。
從我的經驗來看,只是一些被玩家或敵人發射的粒子和投射物被設定為子彈(bullets),通常遊戲的角色不會移動的快到需要將它們設定為子彈(bullets)
-
在 main 函式中僅需要加入一句程式碼:
function main() { world = new b2World(gravity, sleep); debugDraw(); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320 / worldScale, 470 / worldScale); var polygonShape = new b2PolygonShape(); polygonShape.SetAsBox(320 / worldScale, 10 / worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = polygonShape; fixtureDef.density = 1; fixtureDef.restitution = 0.5; fixtureDef.friction = 0.5; var body = world.CreateBody(bodyDef); body.CreateFixture(fixtureDef); bodyDef.position.Set(600 / worldScale, 240 / worldScale); bodyDef.type = b2Body.b2_dynamicBody; polygonShape.SetAsBox(10 / worldScale, 220 / worldScale); var body2 = world.CreateBody(bodyDef); body2.CreateFixture(fixtureDef); bodyDef.position.Set(320 / worldScale, 455 / worldScale); // 加上這一句 bodyDef.bullet = true; polygonShape.SetAsBox(5 / worldScale, 5 / worldScale); var body3 = world.CreateBody(bodyDef); body3.CreateFixture(fixtureDef); body3.SetLinearVelocity(new b2Vec2(100, -10)); stage.addEventListener('click', updateWorld); // setInterval(updateWorld, 1000 / 60); }
在剛體定義中將bullet屬性設定為true將對子彈進行連續碰撞檢測。
現在應該會從障礙物上彈回。
-
測試網頁,來看看發生了什麼。
原始碼:
article/ch07/ch07-2.html
看到了嗎?如你所見,接觸被決算,現在子彈被障礙物彈回。
-
將你的憤怒的小鳥模型中,透過拋擲器發射的投射物,應用bullet屬性,然後將產生一個精確的模擬執行
現在,讓我們來看看最後一個你在 Box2D 可以建立的特殊型別的剛體。
透過感應器檢測接觸,可以允許剛體重疊
在你的遊戲中,有時你可能需要兩個剛體就像沒有發生任何碰撞一樣重疊在一起,同時還能檢測到碰撞。
使用感應器(sensor)可以實現這個功能;
感應器:一個夾具可以在檢測到碰撞的情況下而不作出任何反應。
你可以使用感應器(sensor)建立剛體,所以你將可以在剛體間沒有任何物理觸點的情況下,知道它們之間發生的碰撞。
只要想象一下玩家控制的角色和一個開關:你想要知道當玩家控制的角色撞擊開關觸發一些事件,但是同時你不想開關響應玩家的碰撞。
在最後的指令碼中,我們將測試一個感應器
-
和往常一樣,我們給剛體 userData 屬性中新增剛體的名字
將 barrier 剛體設定為 static 型別並透過 isSensor 屬性定義的它的夾具為感應器。
function main() { world = new b2World(gravity, sleep); debugDraw(); var bodyDef = new b2BodyDef(); bodyDef.position.Set(320 / worldScale, 470 / worldScale); bodyDef.userData = "floor"; var polygonShape = new b2PolygonShape(); polygonShape.SetAsBox(320 / worldScale, 10 / worldScale); var fixtureDef = new b2FixtureDef(); fixtureDef.shape = polygonShape; fixtureDef.density = 1; fixtureDef.restitution = 0.5; fixtureDef.friction = 0.5; fixtureDef.isSensor = true; var body = world.CreateBody(bodyDef); body.CreateFixture(fixtureDef); bodyDef.position.Set(600 / worldScale, 240 / worldScale); bodyDef.userData = "barrier"; polygonShape.SetAsBox(10 / worldScale, 220 / worldScale); var body2 = world.CreateBody(bodyDef); body2.CreateFixture(fixtureDef); bodyDef.position.Set(320 / worldScale, 455 / worldScale); bodyDef.bullet = true; bodyDef.type = b2Body.b2_dynamicBody; bodyDef.userData = "bullet"; polygonShape.SetAsBox(5 / worldScale, 5 / worldScale); fixtureDef.isSensor = false; var body3 = world.CreateBody(bodyDef); body3.CreateFixture(fixtureDef); body3.SetLinearVelocity(new b2Vec2(100, -10)); // 滑鼠多次點選後檢視效果 stage.addEventListener('click', updateWorld); }
為什麼我們要將 barrier 設定為 static 型別呢?
因為它是一個感應器,它的碰撞將不會被決算,所以如果我們設定它為 dynamic 型別,它將不會和地面發生碰撞,所以沒有支撐物,將會一直下落。
將它設定為 static 型別可以確保它固定在一個位置。
-
最後,我們修改 updateWorld() 方法的方式與你在處理檢測碰撞時學到的方法是一樣:
function updateWorld() { world.Step(1 / 30, 10, 10); world.ClearForces(); // 清除作用力 for (var b = world.GetBodyList(); b; b = b.GetNext()) { for (var c = b.GetContactList(); c; c = c.next) { var contact = c.contact; var fixtureA = contact.GetFixtureA(); var fixtureB = contact.GetFixtureB(); var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); var userDataA = bodyA.GetUserData(); var userDataB = bodyB.GetUserData(); if (userDataA == "barrier" || userDataB == "barrier") { console.log(userDataA + "->" + userDataB); } } } world.DrawDebugData(); // 顯示剛體debug輪廓 }
-
我們遍歷所有的剛體,然後遍歷所有的接觸的剛體,當我們發現與障礙物發生碰撞的剛體時,我們將在輸出視窗列印發生的相關資訊
-
測試網頁,然後透過點選使子彈運動:
原始碼:
article/ch07/ch07-3.html
如你所見,子彈穿過障礙物,碰撞被檢測到,並在開發者工具控制檯輸出下面的文字:
bullet->barrier
上面的資訊被輸出了兩次的情況,是因為當拋射物與障礙物發生碰撞時輸出一次,並且障礙物與拋射物發射碰撞時又輸出了一次。
注意:在 Javascript 版中,bullet->barrier 輸出了 14 次,猜測是迴圈中
小結
本章探討了兩個屬性的使用,你學習了在一個離散型模擬中怎樣管理連續碰撞檢測,以及怎樣建立被動剛體(感應器),為了在檢測到碰撞時不要進行決算。
太好了,你們在本書中的學習之旅也在此結束了,但是還有很多Box2D的知識。你也許可以透過本書的知識來製作 Box2D 遊戲,但是程式的世界更新非常之快,你要一直更新自己的知識。
我建議你經常去訪問 www.box2d.org 和 http://box2d.org/manual.pdf 官方網站和文件,同樣我的部落格 www.emanueleferonato.com 也會及時的更新最新的技巧和教程。
一旦你完成了你的第一個 Box2d 遊戲,不要忘記感謝 Erin Catto(類庫的設計者)
本文相關程式碼請在
https://github.com/willian12345/Box2D-for-Javascript-Games
注:轉載請註明出處部落格園:王二狗Sheldon池中物 (willian12345@126.com)