Space Invaders screenshot

In this blog post, I deconstruct a little Space Invaders inspired game I wrote to get to know the physics engine box2dweb. The code builds on the wrapper classes from the excellent Making Games With Box2dWeb article. I strongly recommend reading this article first to understand the basic concepts that I will be using. The topics discussed in this blog post will be

Play the Space Invader game.
Get the source code.


Program Design

The game uses the box2dweb physics engine. I suggest reading this as an introduction. Box2D is a great physics engine written in C++. It is used in games such as Rolando and Angry Birds. Box2dweb is a JavaScript port of an ActionScript port of Box2D. Trust me, it makes sense. Box2dweb has a “world” object that simulates physics in incremental time steps. Basic object types are:

  • Body: bodies are basic objects that model real world objects. You can apply force to a body to make it move.
  • Fixture: fixtures are the actual shapes that can be attached to a body. Collision detection is based on these fixtures. Fixtures can be spheres or polygons, and a body can have more than one fixture with each fixture having different properties such as density.
  • Joint: joints are constraints between bodies. Box2dweb has a variety of joint types such as pulleys, gears, etc.

I am using a slightly modified version of the example5.js file from example 5 from the Making Games With Box2dWeb article. This file defines a Physics class that encapsulates the Box2D world and a Body class that is a convenient wrapper for Box2D bodies. If you only want bodies with simple fixtures (shapes), then using this wrapper can let you forget about fixtures nearly completely. The body wrapper class also provides convenient HTML5 canvas drawing functionality that automatically scales and translates between the physics coordinate system and the canvas.

The Space Invaders game is contained in the Game class. The game is simple enough to not have to break the game logics into Invader, Player, Bullet classes, etc. This is what the modified Physics class expects Game to look like:

var Game = function(physics)
{
	this.setup = function(){};
	this.step = function(dt){};
	this.draw = function(context){};
};

Here is the modified code from the example5.js file

window.gameLoop = function() {
	var tm = new Date().getTime();
	requestAnimationFrame(gameLoop);
	var dt = (tm - lastFrame) / 1000;
	if(dt > 1/15) { dt = 1/15; }
	window.game.step(dt);
	physics.step(dt);
	window.game.draw(physics.context);
	lastFrame = tm;
};
  
function init() {
	physics = window.physics = new Physics(document.getElementById("b2dCanvas"));
	window.game = new Game(window.physics);
	window.game.setup();
	requestAnimationFrame(gameLoop);
}

The init function now no longer creates any geometry or event listeners. Instead, it created a new game and calls its setup function. The game’s setup function now creates all geometry and event listeners for the game.

The gameLoop function calls the game’s step and draw functions. The game’s step function is the only place where the game can manipulate the box2dweb world.


User Input

The Space Invader game has a player object at the bottom of the screen that can move left and right and shoot bullets. It is controlled via the keyboard.

The player object is created in the game’s setup function:

...
this.player = new Body(this.physics, { color: "green", x: 20, y: 29.5, height: 1,  width: 5 });

var jointDefinition = new Box2D.Dynamics.Joints.b2MouseJointDef();
jointDefinition.bodyA = this.physics.world.GetGroundBody();
jointDefinition.bodyB = this.player.body;
jointDefinition.target.Set(20,29.5);
jointDefinition.maxForce = 100000;
jointDefinition.timeStep = this.physics.stepAmount;
this.player.joint = this.physics.world.CreateJoint(jointDefinition);
...			
window.addEventListener("keydown", keyDown);
window.addEventListener("keyup", keyUp);
...

The player object is an instance of the wrapper class Body as defined in example5.js.

The joint that is attached to it is a MouseJoint which allows us to pin the player’s body to a certain location. In Box2D, it is recommended to not directly set the location of a body during the simulation. Instead, various types of joints are used. The MouseJoint is for example convenient for dragging bodies with the mouse and I am using it to move the player left and right with the keyboard. I store the joint as a property on the player object so it is easy to manipulate it later on.

The keyboard events are captured with normal event listeners. Here is the keyDown listener:

function keyDown(event)
{
	var keyCode = ('which' in event) ? event.which : event.keyCode;
	if(keyCode == 37)
	{
		self.leftDown = true;
		return false;
	} 
	if(keyCode == 39)
	{
		self.rightDown = true;
		return false;
	} 
	if(keyCode == 32)		
	{
		self.spaceDown = true;
		return false;
	}
	return true;
};

As you can see, the player is not actually manipulated in this method. The same goes for the keyUp method which is the mirror image of keyDown.

This is because manipulation of box2dweb objects must occur outside of the physics.step method. As a consequence, the game only performs box2dweb manipulations in the game.step method. As shown further above, the game.step method is always called right before the physics.step method.

So how is the player moved around in the game.step method?

...
var joint = this.player.joint;
var pos = joint.GetTarget();
if(this.leftDown)
{
	joint.SetTarget(new b2Vec2(pos.x - dt * 10, pos.y));
} 
if(this.rightDown)
{
	joint.SetTarget(new b2Vec2(pos.x + dt * 10, pos.y));
} 
...

The target of the player’s MouseJoint is moved left or right depending on whether the left or the right key is pressed.

The value of dt is the time since the last step in seconds. This value also gets passed on to the physics step() method. In the game’s step() method, it is used to ensure even movement.


Shooting Bullets

The next step is to make the player object shoot. This actually quite straightforward. The following code snippet is executed right after the previous one:

if(this.spaceDown)
{
	var dFire = ((new Date().getTime()) - this.lastFired) / 1000;
	
	if(dFire >= 0.3)
	{
		var bullet = new Body(physics, { color: "green", type: "dynamic", IsBullet: true, x: pos.x, y:pos.y-2, height: 1, width: 0.25 });	
		bullet.body.ApplyImpulse({x:0,y:-80}, bullet.body.GetWorldCenter());	
		this.lastFired = new Date().getTime();
	}
}

If the space bar is pressed, and if at least 0.3 seconds have elapsed since the last shot, a new body is created. This body has one special attribute set: IsBullet. This helps box2dweb test for collision of our bullet with other objects. Bullets are assumed to be small and travel fast, a combination that can make collision detection tricky. After creating the bullet, an impulse is applied. The direction is straight up, and the magnitude is a value that I think feels right.

Note how the choice of keyboard event handling lets users fire bullets while also moving the player object at the same time.

And yes, this means that a new bullet is created every time the player shoots. A more efficient implementation might have a cache of bullets that can be re-used. Constantly creating new bullets means they need to be destroyed once they are no longer used. I will discuss this further down.


Floating Invaders

Now to give the player object something to shoot at: alien invaders floating down from space. Maybe my Google skills have left me hanging here again, but every advice I could find suggested applying force in the opposite of the gravity vector. This proved tricky for several reasons. The best result I could achieve were invaders that slowly sunk to the ground in Firefox and slowly floated upwards in Chrome. Maybe this can be achieved more reliably in the original Box2D implementation. But anyway, as soon as the player started shooting at them, the invader formation was in tatters with some floating off screen, and other crashing to the ground with the weight of shrapnel resting on them. Messy calculation and application of various forces to each invader at every step would have been necessary.

So instead I let box2dweb do the work. That’s why I’m using a physics framework: so that I don’t have to do the work. Here is the function that creates one space invader:

this.createInvader = function(img, xPos, yPos)
{
	var invader = new Body(window.physics, { image: img, x: xPos, y: yPos, width: 5.56*0.7, height: 4.1*0.7, fixedRotation:true  });
	
	var distJointDef = new Box2D.Dynamics.Joints.b2DistanceJointDef();
	distJointDef.Initialize(invader.body,
	this.invaderFormation.body,
	new b2Vec2(xPos, yPos),
	new b2Vec2(xPos, yPos - 0.2));
	distJointDef.dampingRatio = 0.05;
	distJointDef.frequencyHz = 1;
	invader.joint = this.physics.world.CreateJoint(distJointDef);		
	...
};

First, a new body is created. Note the special attribute fixedRotation (you may have to scroll right to see it). This ensures that the invader will always stays horizontal.

Then, the invader is hung on a short bungee cord. This allows the invader to move when it collides with another body and box2dweb will take care of bouncing it back to its proper spot. A DistanceJoint can work like a rubber band with the right dampingRatio and frequencyHz values.

You may have noticed that the joint is between the newly created invader and a body called “invaderFormation”. Since the invaders are supposed to move in formation from left to right and downwards, they are all hung from a big rectangle stored in the invaderFormation variable. Then in order to move the invaders together, this big rectangle is moved using a MouseJoint which is what is used to control the player object as well:

this.moveInvaders = function(dt)
{
	var joint = this.invaderFormation.joint;
	var pos = joint.GetTarget();
	var down = 0;
	if(this.formationDelta < -3)
	{
		this.formationDirection = 1.5;
		down = 0.7;
	}
	else if (this.formationDelta > 3)
	{
		this.formationDirection = -1.5;
		down = 0.7;
	}
	this.formationDelta += (dt/this.formationDirection);
	joint.SetTarget(new b2Vec2(pos.x + (dt/this.formationDirection), pos.y + down));
};

The moveInvaders function is of course called by the game.step method because this is the only place that the box2dweb world can be manipulated. dt is again the time since that last step in seconds. This value is used to create an even movement.

So now you can explain the weird movement of the invaders: they are hung from a big rectangle by rubber bands, and the rectangle is moved left, right, and down.

The invader movement is rather rocky. Maybe I should have used box2dweb controllers to smoothly control them. Ah well, there is always time for one more blog post about box2dweb!


Collision Detection

This is where it gets exciting! So far, I have explained how to have a player object that can move based on keyboard input, that can shoot, and how we can have invaders floating above the player object. At this stage, the player can already shoot at the invaders, and the invaders will start bouncing on their rubber bands when they are hit by a bullet. The next step is to make the invaders explode once they are hit by a bullet.

Box2dweb handles collisions between bullets and invaders nicely, but the game object knows nothing about these collisions yet. Box2dweb offers several hooks into collision detection and this game is using the PostSolve event. At this stage, box2dweb knows which objects have collided and what the impact force is. I ended up not using the impact force (or impulse) so I could have used one of the simpler methods instead.

First, collision handling is set up in the game.setup function:

var collisionListener = new Box2D.Dynamics.b2ContactListener();
collisionListener.PostSolve = collision;
self.physics.world.SetContactListener(collisionListener);

The collision method looks like this:

function collision(contact,impulse) 
{
	var bodyA = contact.GetFixtureA().GetBody().GetUserData();
	var bodyB = contact.GetFixtureB().GetBody().GetUserData();
		  
	if(bodyA.invaderType == types.BULLET)
	{
		self.handleBulletCollision(bodyA, bodyB);
	}
	else if(bodyB.invaderType == types.BULLET)
	{
		self.handleBulletCollision(bodyB, bodyA);
	}
	...
};

GetUserData() returns the body wrapper object. types.BULLET is defined in an enum in the Game class:

var types = {	
	INVADER : 0,
	PLAYER : 1,
	BULLET : 2,
	SHRAPNEL : 3,
	...
}

What I have skipped over so far is that whenever the game creates a body, it sets the body’s invaderType to one of the values listed in types. This allows the collision method to quickly determine whether it is interested in a collision. Collision events are fired very often, and fast code is critical.

You will have noticed that the code checks whether bodyA is a bullet and whether bodyB is a bullet. The Box2D documentation does not specify in which order bodyA and bodyB will be given to the event handler so the game has to check both cases. Also keep in mind that the same pair of bodies can collide more than once per step.

Now have a look at the handleBulletCollision function that is called from the collision method if one of the bodies is a bullet:

this.handleBulletCollision = function(bullet, other)
{
	if(other.invaderType == types.INVADER)
	{
		this.explodeBodies.push(other);
		this.score += 100;
	}
	...
	this.garbageBodies.push(bullet);
};

As expected, the state of the box2dweb world is not manipulated. This is expressly forbidden during collision events by the Box2D documentation. And as with keyboard events, box2dweb manipulation only happens in the game.step method.

You can see that if a bullet hits an invader, the invader is pushed onto the explodeBodies array. And if a bullet hits anything, it is pushed onto the garbageBodies array. I will discuss the use of the garbageBodies array in the next section. For now, here is a simplified version of what happens to the explodeBodies array in the game.step function:

this.cleanup = function()
{
	for(var i = 0; i < this.explodeBodies.length; i++)
	{
		var body = this.explodeBodies[i];
		if(body.body != null)
		{
			var pos = body.body.GetWorldCenter();
			var shrapnelCount = 30;
			var shrapnelForceX = 40;
			var shrapnelForceY = 0;
			var color = "white";
			for(var c = 0; c < shrapnelCount; c++) {
				var angle = c*2*3.14/shrapnelCount;
				var radius = 0.1;
				var x = (Math.cos(angle) * radius);
				var y = (Math.sin(angle) * radius)
				var shrapnelObject = new Body(window.physics, { 'color': color, x: pos.x+x, y: pos.y+y, height: 0.5,  width: 0.5 });
				shrapnelObject.invaderType = this.SHRAPNEL;
				shrapnelObject.body.ApplyImpulse({'x': x * shrapnelForceX * Math.random(), 'y': x * shrapnelForceY * Math.random()}, shrapnelObject.body.GetWorldCenter());
				this.shrapnel.push(shrapnelObject);
			}			
			if(body.invaderType != types.PLAYER)
			{
				this.destroyBody(body);
			}
		}
	}
	this.explodeBodies = [];	
};

The cleanup function is called from the game.step function. In this version, it goes through all bodies in the explodeBodies array (so far only invaders) and “explodes” them. What actually happens is that a number of small “shrapnel” objects are created in a small circle around the center of the body that is to be exploded. Then an impulse outward from the center is applied to each of these particles.

Here you can also see how the invaderType is set when a body is created.

For housekeeping, each piece of shrapnel is pushed onto the shrapnel array, and the exploded body is destroyed. This is both discussed in the next section.


Object Destruction

The game as described so far creates a large number of box2dweb objects. Every time the player shoots, a new bullet is created. Every time an invader is hit, 30 pieces of shrapnel are created. The complete game creates even more of these objects. At some stage the box2dweb engine and the browser will reach a limit and a real-time simulation is no longer possible. This means that the game needs to be able to delete objects that are no longer used to keep the number of box2dweb objects low.

Box2dweb objects are not simply garbage collected when your application no longer holds a reference to it because the box2dweb world will still reference them. This means a good scheme for safe object disposal is required. The game needs to make sure that there is no memory leak and that box2dweb objects are no longer used after their destruction.

The box2dweb world object offers the functions DestroyBody and DestroyJoint. The Space Invaders game uses both of these in a small function to safely destroy the body wrapper classes:

this.destroyBody = function(body)
{
	if(body.joint != null)
	{
		this.physics.world.DestroyJoint(body.joint);
		body.joint = null;
	}
	if(body.body != null)
	{
		this.physics.world.DestroyBody(body.body);
		body.body = null;
	}
};

First, the joint that is stored on the wrapper class is destroyed if it still exists. Note how after destruction, body.joint is set to null so that it can’t be destroyed more than once. Trying to destroy joints and bodies more than once will lead to interesting results and must be avoided for a bug free game. The body is then destroyed the same way as the joint.

I would expect box2dweb to automatically destroy the joint if one of its bodies is destroyed, but the documentation does not talk about this. So I always delete the joint just to be sure.

Inside the cleanup function that also explodes bodies as seen above, this code does the housekeeping:

for(var i = 0; i < this.garbageBodies.length; i++)
{
	this.destroyBody(this.garbageBodies[i]);
}
this.garbageBodies = [];

while(this.shrapnel.length > 150)
{
	this.destroyBody(this.shrapnel.shift());
}

First, all objects in the garbageBodies array are destroyed. This will be bullets and space invaders.

Then, the oldest entries from the shrapnel array are destroyed until not more than 150 are left. Remember that new shrapnel is pushed onto this array. I found that the performance of the game declined dramatically at about 200 pieces of shrapnel, so now the game ensures that there will be no more than 150 of them.

So there you have it. You can control a player object, dangle invaders from the sky, shoot at them, and make them explode. It’s by no means a complete game – once you kill all the invaders there is no second level. It’s just me having a play with box2dweb. Try out the game again, examine the source code, and write your own game!

Play the Space Invader game.
Get the source code.

Advertisements