Talking about Box2D, HTML5, Javascript and Phaser.
I am a long time fan of Box2D physics engine, with almost 200 tutorials on this blog, most using old AS3, about 10 Flash games and five star book on Amazon.
Unfortunately Phaser 3 does not include any Box2D native wrapper as it does with Matter physics.
This does not mean we cannot use Box2D in our projects, it’s just we don’t have dedicated Phaser method to handle Box2D.
And, above all, we do not even have a native JavaScript version of Box2D.
There are some JavaScript ports of Box2D around the web, and the one I like the most is Planck.js by Ali Shakiba.
Planck.js is a JavaScript rewrite of Box2D physics engine while other JavaScript libraries are direct portings of the original C++ engine.
In this first example, something like a “Hello World”, I will show you these concepts:
* Create a Box2D world
* Add gravity
* Convert meters to pixels
* Add a body to the world
* Set a body as static or dynamic
* Add a rectangle fixture to a body
* Set body mass
* Set a body user data to store body graphic assets
* Update the simulation
* Iterate through bodies
* Adjust graphics assets according to the simulation
Like most physics engines, Box2D does not render graphics, it just does the math and physics work, and it’s up to you to update the canvas.
Have a look:
There’s no interactivity, just watch and remember good old Flash/Box2D days, which will probably have a little comeback because I am going to rewrite some old Box2D tutorials.
Look at the completely commented source code:
let game;
window.onload = function() {
let gameConfig = {
type: Phaser.AUTO,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: "thegame",
width: 600,
height: 600
},
scene: playGame
}
game = new Phaser.Game(gameConfig);
window.focus();
}
class playGame extends Phaser.Scene{
constructor(){
super("PlayGame");
}
create(){
// Box2D works with meters. We need to convert meters to pixels.
// let's say 30 pixels = 1 meter.
this.worldScale = 30;
// world gravity, as a Vec2 object. It's just a x, y vector
let gravity = planck.Vec2(0, 3);
// this is how we create a Box2D world
this.world = planck.World(gravity);
// createBox is a method I wrote to create a box, see how it works at line 55
this.createBox(game.config.width / 2, game.config.height - 20, game.config.width, 40, false);
// the rest of the script just creates a random box each 500ms, then restarts after 100 iterations
this.tick = 0;
this.time.addEvent({
delay: 500,
callbackScope: this,
callback: function(){
this.createBox(Phaser.Math.Between(100, game.config.width - 100), -100, Phaser.Math.Between(20, 80), Phaser.Math.Between(20, 80), true);
this.tick ++;
if(this.tick == 100){
this.scene.start("PlayGame");
}
},
loop: true
});
}
// here we go with some Box2D stuff
// arguments: x, y coordinates of the center, with and height of the box, in pixels
// we'll conver pixels to meters inside the method
createBox(posX, posY, width, height, isDynamic){
// this is how we create a generic Box2D body
let box = this.world.createBody();
if(isDynamic){
// Box2D bodies born as static bodies, but we can make them dynamic
box.setDynamic();
}
// a body can have one or more fixtures. This is how we create a box fixture inside a body
box.createFixture(planck.Box(width / 2 / this.worldScale, height / 2 / this.worldScale));
// now we place the body in the world
box.setPosition(planck.Vec2(posX / this.worldScale, posY / this.worldScale));
// time to set mass information
box.setMassData({
mass: 1,
center: planck.Vec2(),
// I have to say I do not know the meaning of this "I", but if you set it to zero, bodies won't rotate
I: 1
});
// now we create a graphics object representing the body
var color = new Phaser.Display.Color();
color.random();
color.brighten(50).saturate(100);
let userData = this.add.graphics();
userData.fillStyle(color.color, 1);
userData.fillRect(- width / 2, - height / 2, width, height);
// a body can have anything in its user data, normally it's used to store its sprite
box.setUserData(userData);
}
update(){
// advance the simulation by 1/20 seconds
this.world.step(1 / 30);
// crearForces method should be added at the end on each step
this.world.clearForces();
// iterate through all bodies
for (let b = this.world.getBodyList(); b; b = b.getNext()){
// get body position
let bodyPosition = b.getPosition();
// get body angle, in radians
let bodyAngle = b.getAngle();
// get body user data, the graphics object
let userData = b.getUserData();
// adjust graphic object position and rotation
userData.x = bodyPosition.x * this.worldScale;
userData.y = bodyPosition.y * this.worldScale;
userData.rotation = bodyAngle;
}
}
};
Probably it’s just me, but just declaring a worldScale
variable makes me happy. Download the source code.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.