Build a HTML5 game like old Flash glory “Totem Destroyer” using Phaser and Planck.js physics engine
Do you remember Totem Destroyer? The link points to a YouTube video, because being a Flash game, it’s not longer playable.

Your mission is to destroy a series of totems without letting the golden Idol (aka Tot) fall into the ground.
I started writing about this game back in 2008 – 13 years ago! – building a prototype using Flash and AS3 which obviously now it doesn’t work anymore.
If you follow my tutorial series about Totem Destroyer, you’ll see I built prototypes using Flash, Dreemchest (which is dead nowadays), PhysicsJS and Phaser 2 with p2.js.
The game is fun and the prototypes are definitively outdated, so here is a new prototype using Phaser 3 and Box2D powered by Planck.js.
Click on a light brick to destroy it. Light bricks are the only ones which can be destroyed. Don’t let the idol hit the ground.
Although it’s a simple prototype, it requires these features
* Queries to check for bodies under the pointer
* Custom data management for each body
* Collision detection
And they all are explained line by line in the commented source code:
let game;
let gameOptions = {
// conversion unit from pixels to meters. 30 pixels = 1 meter
worldScale: 30
}
window.onload = function() {
let gameConfig = {
type: Phaser.AUTO,
backgroundColor:0x87ceea,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: "thegame",
width: 800,
height: 400
},
scene: playGame
}
game = new Phaser.Game(gameConfig);
window.focus();
}
// constants to store block types
const TERRAIN = 0;
const IDOL = 1;
const BREAKABLE = 2;
const UNBREAKABLE = 3;
class playGame extends Phaser.Scene {
constructor() {
super("PlayGame");
}
create() {
// 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);
// totem creation
this.createBox(game.config.width / 2, game.config.height - 20, game.config.width, 40, false, TERRAIN, 0x049b15);
this.createBox(game.config.width / 2 - 60, game.config.height - 60, 40, 40, true, BREAKABLE, 0x6e5d42);
this.createBox(game.config.width / 2 + 60, game.config.height - 60, 40, 40, true, BREAKABLE, 0x6e5d42);
this.createBox(game.config.width / 2, game.config.height - 100, 160, 40, true, BREAKABLE, 0x6e5d42);
this.createBox(game.config.width / 2, game.config.height - 140, 80, 40, true, UNBREAKABLE, 0x3b3b3b);
this.createBox(game.config.width / 2 - 20, game.config.height - 180, 120, 40, true, BREAKABLE, 0x6e5d42);
this.createBox(game.config.width / 2, game.config.height - 240, 160, 80, true, UNBREAKABLE, 0x3b3b3b);
this.idol = this.createBox(game.config.width / 2, game.config.height - 320, 40, 80, true, IDOL, 0xfff43a);
// input listener to call destroyBlock method
this.input.on("pointerdown", this.destroyBlock, this);
}
// method to destroy a block
destroyBlock(e) {
// convert pointer coordinates to world coordinates
let worldX = this.toWorldScale(e.x);
let worldY = this.toWorldScale(e.y);
let worldPoint = planck.Vec2(worldX, worldY);
// query for the world coordinates to check fixtures under the pointer
this.world.queryAABB(planck.AABB(worldPoint, worldPoint), function(fixture) {
// get the body from the fixture
let body = fixture.getBody();
// get the userdata from the body
let userData = body.getUserData();
// is a breakable body?
if (userData.blockType == BREAKABLE) {
// destroy the sprite
userData.sprite.destroy();
// destroy the body
this.world.destroyBody(body);
}
}.bind(this));
}
// simple function to convert pixels to meters
toWorldScale(n) {
return n / gameOptions.worldScale;
}
// totem block creation
createBox(posX, posY, width, height, isDynamic, blockType, color) {
// 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 / gameOptions.worldScale, height / 2 / gameOptions.worldScale));
// now we place the body in the world
box.setPosition(planck.Vec2(posX / gameOptions.worldScale, posY / gameOptions.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
let borderColor = Phaser.Display.Color.IntegerToColor(color);
borderColor.darken(20);
let userData = {
blockType: blockType,
sprite: this.add.graphics()
}
userData.sprite.fillStyle(color);
userData.sprite.fillRect(- width / 2, - height / 2, width, height);
userData.sprite.lineStyle(4, borderColor.color)
userData.sprite.strokeRect(- width / 2 + 2, - height / 2 + 2, width - 4, height - 4);
// a body can have anything in its user data, normally it's used to store its sprite
box.setUserData(userData);
return box;
}
update(t, dt) {
// advance world simulation
this.world.step(dt / 1000 * 2);
// crearForces method should be added at the end on each step
this.world.clearForces();
// get idol contact list
for (let ce = this.idol.getContactList(); ce; ce = ce.next) {
// get the contact
let contact = ce.contact;
// get the fixture from the contact
let fixture = contact.getFixtureA();
// get the body from the fixture
let body = fixture.getBody();
// the the userdata from the body
let userData = body.getUserData();
// did the idol hit the terrain?
if (userData.blockType == TERRAIN) {
// oh no!!
this.cameras.main.setBackgroundColor(0xa90000)
}
}
// 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.sprite.x = bodyPosition.x * gameOptions.worldScale;
userData.sprite.y = bodyPosition.y * gameOptions.worldScale;
userData.sprite.rotation = bodyAngle;
}
}
};
And old Flash glory could revive thanks to Phaser and Planck.js
Do you have a map of all original levels? Download the source code and try to build your own Totem Destroyer game.