
Box2D merupakan salah satu library
physic 2D yang cukup populer digunakan dalam game-game 2D. Colin Northway kemudian mengadaptasikan library buatan Erin Catto ini kedalam format ActionScript 3 yang bisa digunakan untuk aplikasi Flash.
Pada kesempatan kali ini, kita akan mempelajari konsep penggunaan Box2D serta implementasi basic library tersebut didalam Flash. Saya akan menggunakan FlashDevelop pada tutorial ini.
Perlu diingat bahwa Box2D hanya digunakan untuk simulasi physic. Tapi kita bisa menggerakkan Sprite/MovieClip berdasarkan data physic yang disimulasikan oleh Box2D (posisi dan rotasi objek physic).
Pertama-tama kita membutuhkan library Box2D untuk ActionScript 3. Kita bisa mengunduhnya melalui http://sourceforge.net/projects/box2dflash.

Akan lebih baik bila anda bisa melakukan update langsung dari repository SVN proyek ini.
Extract file zip yang anda dapatkan di tempat yang mudah dijangkau, misalnya C:/flash/box2d/ .
Buka FlashDevelop, lalu pilih Project -> New Project, lalu pilih AS3 Project. Beri nama project dengan BeginningBox2D, lalu pilih lokasi project yang anda inginkan. Tekan Ok.

Pertama-tama kita perlu menambahkan library Box2D kedalam project ini. Pilih Project -> Properties.
Pada layar setting BeginningBox2D(AS3), pilih tab Classpaths, lalu klik tombol Add Classpath… Arahkan pada lokasi library Box2D, dalam hal ini di C:/flash/box2d/ . Tekan Ok.

Buka file Main.as yang ada di folder src pada panel Project.

Ketik script berikut untuk mengganti keseluruhan script yang telah ada:
package {
import Box2D.Collision.b2AABB;
import Box2D.Collision.b2Bound;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2DebugDraw;
import Box2D.Dynamics.b2World;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Main extends Sprite
{
public var world:b2World;
public var timeStep:Number = 1.0 / 30.;
public var iterationCount:Number = 10;
public function Main():void
{
initWorld();
initStaticBody();
initDynamicBody();
initDebugDraw();
addEventListener(Event.ENTER_FRAME, onUpdate);
}
private function onUpdate(event:Event):void
{
world.Step(timeStep, iterationCount, iterationCount);
}
private function initWorld():void
{
var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set(-100, -100);
worldAABB.upperBound.Set(100, 100);
var gravity:b2Vec2 = new b2Vec2(0, 10);
var doSleep:Boolean = true;
world = new b2World(worldAABB, gravity, doSleep);
}
private function initStaticBody():void
{
var groundBodyDef:b2BodyDef = new b2BodyDef();
groundBodyDef.position.Set(5, 10);
var groundBody:b2Body = world.CreateBody(groundBodyDef);
var groundShapeDef:b2PolygonDef = new b2PolygonDef();
groundShapeDef.SetAsBox(5, 1);
groundBody.CreateShape(groundShapeDef);
}
private function initDynamicBody():void
{
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(5, 5);
bodyDef.angle = 50*Math.PI/180;
var body:b2Body = world.CreateBody(bodyDef);
var shapeDef:b2PolygonDef = new b2PolygonDef();
shapeDef.SetAsBox(1, 1);
shapeDef.density = 1;
shapeDef.friction = 0.3;
body.CreateShape(shapeDef);
body.SetMassFromShapes();
}
private function initDebugDraw():void
{
var debugSprite:Sprite = new Sprite();
addChild(debugSprite);
var debugDraw:b2DebugDraw = new b2DebugDraw();
debugDraw.SetSprite(debugSprite);
debugDraw.SetDrawScale(30);
debugDraw.SetFillAlpha(0.3);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);
}
}
}
Diagram dasar dari workflow Box2D:

Berikut penjelasan dari script diatas.
Pada function initWorld(), kita membuat definisi dunia physic yang nantinya akan ditempati oleh objek-objek physic lainnya. Perlu diperhatikan bahwa unit dalam Box2D menggunakan skala MKS. 1 pixel tidak sama dengan 1 unit dalam Box2D. Sebagai patokan dasar, 30 pixel sama dengan 1 unit dalam Box2D (ini bisa dirubah lagi sesuai keperluan kita).
var worldAABB:b2AABB = new b2AABB(); worldAABB.lowerBound.Set(-100, -100); worldAABB.upperBound.Set(100, 100); var gravity:b2Vec2 = new b2Vec2(0, 10); var doSleep:Boolean = true; world = new b2World(worldAABB, gravity, doSleep);
Variabel worldAABB menentukan batas bawah (lowerBound) dan batas atas (upperBound) dari dunia physic yang kita buat. Perlu diingat bahwa orientasi arahnya mirip Flash, jadi perhitungan arah akan negatif bila bergerak ke kiri atas, dan akan positif bila bergerak ke kanan bawah. Dalam hal ini, batas lowerBound (-100,-100) berada pada pojok kiri atas dan upperBound (100,100) berada pada pojok kanan bawah.
Variabel gravity merupakan penentu efek gravitasi dalam dunia physic ini. Dalam hal ini, kita membuat dunia dengan gravitasi berkekuatan 10 ke arah bawah, mendekati dunia nyata.
Variabel doSleep merupakan penentu apakah physic body yang tidak bergerak lagi terus disimulasikan atau tidak. Bila diset true, maka physic body yang tidak bergerak akan “ditidurkan” dan tidak akan diperhitungkan dalam pemrosesan dunia physic ini untuk menghemat komputasi.
Pada function initStaticBody(), kita membuat definisi untuk objek static yang tidak disimulasikan oleh Box2D. Perbedaan dari static dan dynamic body adalah kita tidak perlu mengeset density dan mass dari static body.
var groundBodyDef:b2BodyDef = new b2BodyDef(); groundBodyDef.position.Set(5, 10); var groundBody:b2Body = world.CreateBody(groundBodyDef); var groundShapeDef:b2PolygonDef = new b2PolygonDef(); groundShapeDef.SetAsBox(5, 1); groundBody.CreateShape(groundShapeDef);
Seperti dijelaskan pada diagram diatas, kita buat body definition terlebih dahulu, dan meletakkan posisinya pada x = 5 unit, dan Y = 10 unit. Atau dalam satuan pixel Flash standar, x = 150 dan y = 300.
Kita membuatnya dengan menggunakan world.CreateBody(groundBodyDef) yang mengembalikan sebuah physic body baru yang akan kita pakai sebagai groundBody.
Selanjutnya, kita membuat shape definition berdasarkan b2PolygonDef, dan membuat kotak dengan function SetAsBox(5,1), dimana 5 adalah lebar dan 1 adalah tinggi shape pada satu sisi. Karena objek Box2D bersumbu pada bagian tengah objek, maka lebar dan tinggi aktualnya adalah 10 x 2 unit. Atau dalam satuan pixel Flash standar ukurannya 300 x 60 pixel.
Terakhir, kita buat groundBody mengimplementasikan shape tersebut.
Pada function initDynamicBody(), kita membuat definisi untuk objek dynamic yang akan disimulasikan oleh Box2D.
var bodyDef:b2BodyDef = new b2BodyDef(); bodyDef.position.Set(5, 5); bodyDef.angle = 50*Math.PI/180; var body:b2Body = world.CreateBody(bodyDef); var shapeDef:b2PolygonDef = new b2PolygonDef(); shapeDef.SetAsBox(1, 1); shapeDef.density = 1; shapeDef.friction = 0.3; body.CreateShape(shapeDef); body.SetMassFromShapes();
Mirip dengan pembuatan static body diatas, kita membuat body definition (kali ini ditambah dengan mengatur rotasi objek, 50 derajat yang perlu diubah kedalam format radian), membuat physic body, membuat shape definition dengan bentuk kotak berukuran 1 x 1 unit.
Yang membedakan dengan static body diatas adalah, kita mengeset density dari objek physic ini. Friction merupakan parameter tambahan yang mengatur seberapa licin permukaan objek tersebut.
Setelah mengimplementasikan shape definition pada physic body, kita juga perlu mengeset massa dari objek tersebut. Kita bisa mengaturnya manual, atau menghitungnya secara otomatis menggunakan function SetMassFromShapes(). Dengan ini, massa dari physic body tersebut konsisten dengan bentuk shape definition yang dimilikinya.
Untuk menjalankan simulasi dunia physic Box2D, kita perlu menambahkan penanganan event Event.ENTER_FRAME, dan memasukkan code berikut pada function listenernya:
world.Step(timeStep, iterationCount, iterationCount);
Variabel timeStep merupakan satuan waktu dimana anda ingin physic world di-update. Idealnya angka ini sebanding dengan framerate aplikasi anda. Jadi bila framerate 30fps (30 frame per detik), timeStep lebih baik diisi 1/30.
Variabel iterationCount merupakan jumlah pengecekan physic world terhadap velocity (kecepatan) dan posisi physic body. Makin besar iterationCount, perhitungannya akan makin presisi, tapi dengan drawback penurunan performa. Nilai 10 biasanya sudah cukup ideal digunakan sebagai iterationCount.
Semua script diatas sebenarnya sudah cukup untuk menjalankan simulasi physic Box2D. Tapi karena belum ada Sprite/MovieClip yang dipasangkan pada physic body, maka kita tidak bisa melihat hasilnya. Alternatif yang bisa kita pakai untuk bisa melihat hasil simulasi physic Box2D dengan cepat adalah dengan mengaktifkan fitur debug draw.
Berikut script pada function initDebugDraw()
var debugSprite:Sprite = new Sprite(); addChild(debugSprite); var debugDraw:b2DebugDraw = new b2DebugDraw(); debugDraw.SetSprite(debugSprite); debugDraw.SetDrawScale(30); debugDraw.SetFillAlpha(0.3); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw);
Kita membuat Sprite baru yang kemudian ditambahkan dalam display list dengan function addChild().
Setelah itu kita membuat variabel b2DebugDraw baru, dan mengatur berbagai parameter yang diperlukan. Sprite, draw scale (samakan dengan skala standar 1 unit = 30 pixel) 30, fill alpha (transparansi) 0.3, dan set flags (apa saja shape yang ditampilkan) b2DebugDraw.e_shapeBit dan b2DebugDraw.e_jointBit.
Terakhir, kita hanya perlu memanggil function world.SetDebugDraw(debugDraw) untuk mengaktifkan fitur debug draw tersebut.

Sebenarnya masih banyak lagi hal menarik yang bisa diekplorasi dari Box2D, dan bukan tidak mungkin anda bisa membuat game-game bertema physic menarik lain seperti Crayon Deluxe . Joint, Lever, Contact, dan berbagai fitur lainnya menunggu untuk anda pelajari J
Berikut aplikasi DemoBox2D yang menggunakan konsep game Totem, dimana anda harus menghancurkan semua blok penopang yang ada tanpa membiarkan kepala totem terjatuh ke tanah.

File proyek: