Creating a game like Tanks / Worms, Part 2

By Richard Davey on 16th January 2015   @photonstorm

Welcome!

Missed Issue 1? Read it before continuing. It contains Part 1 of the Tanks game.

I was really pleased with the reception the first issue received! It seems I wasn't the only one looking for small and focused tutorials. In this issue we complete the tanks game, although there's plenty of scope left for you to push it further. If you do so please share the end result!

Tanks - Part 2

In Part 1 we got the tank displayed and firing a single bullet at some targets. Now it's time to make it a proper tanks game and add a landscape!

Tanks

Graphics from Amiga Tanx Copyright 1991 Gary Roberts

Get the source

I'd suggest you have a play of the game and a quick scroll through the code. I'm only going to highlight the most important parts here, or those that need more explanation. If you've questions about a part I didn't include then please use the forum to ask it.

Run and live-edit the code on jsbin or codepen. You can also clone the git repo.

create

The create method is almost identical to Part 1. The difference is that the targets are now positioned so they lay on the new landscape.

We've also added two new Game Objects: the land and a particle emitter. The land is a BitmapData object to which we draw our land.png file. This PNG has the landscape drawn on a transparent background. It's drawn exactly as it appears in the screen shot above (feel free to edit it!)

this.land = this.add.bitmapData(992, 480);
this.land.draw('land');
this.land.update();
this.land.addToWorld();

After we've drawn the PNG to the BitmapData we have to update it. This is because we need to access its pixel data during the game. The final line adds it to the game world. Internally this creates a new Sprite object, sets the BitmapData to be its texture and adds it to the Game World at 0, 0 (because we didn't specify any other location).

That's all we need to visually add the land, but how do we destroy it?

bulletVsLand()

If you look at the update method you'll see that we're checking if the bullet exists. If so we check it against the targets and then the land:

if (this.bullet.exists)
{
    this.physics.arcade.overlap(this.bullet, this.targets, this.hitTarget, null, this);

    this.bulletVsLand();
}

The bulletVsLand method starts with a simple bounds check. If the bullet goes out of bounds then we kill it and return (as it can't now hit the land). Notice that we don't check the 'top' of the world, as we want it to be allowed to rise up above the screen and fall back down again.

if (this.bullet.x < 0 || this.bullet.x > this.game.world.width || this.bullet.y > this.game.height)
{
    this.removeBullet();
    return;
}

The next part is the meaty bit:

var x = Math.floor(this.bullet.x);
var y = Math.floor(this.bullet.y);
var rgba = this.land.getPixel(x, y);

if (rgba.a > 0)
{
    this.land.blendDestinationOut();
    this.land.circle(x, y, 16, 'rgba(0, 0, 0, 255');
    this.land.blendReset();
    this.land.update();

    this.removeBullet();
}

Because we're going to be doing a pixel color look-up on the BitmapData we have to floor the bullet coordinates. Once done we can use the BitmapData.getPixel method to get a Color object for the given pixel. This is done every frame as the bullet flies through the air, we sample the pixel color beneath it.

Our PNG is a landscape drawn on a transparent background, so all we need to do is check that we're over a pixel that has an alpha value greater than zero. If this is the case we blow a chunk out of the land.

This is done by setting the destination-out blend mode. If you draw on a canvas with this blend mode you can effectively "remove" parts of it. In this case we'e drawing a 16px sized circle where the bullet landed. Combine this with the blend mode and you punch a small hole into the land.

The final few lines reset the blend mode and call BitmapData.update which tells it to rescan the pixel data and render the new scene. Finally the bullet is removed, its job done.

Using this approach you can soon make Swiss-cheese of the landscape:

holy moly

Feel free to vary the circle size! Or even draw an entirely different shape.

Great balls of fire

With the land reacting to our bullets we can ice this cake by adding an explosion effect when we hit a target. For this we'll use an Emitter. We make it in the create method:

this.emitter = this.add.emitter(0, 0, 30);
this.emitter.makeParticles('flame');
this.emitter.setXSpeed(-120, 120);
this.emitter.setYSpeed(-100, -200);
this.emitter.setRotation();

It's re-using the flame.png which we use for the tank fire effect. Rotation is disabled by calling setRotation with no parameters. When the particles emit they'll pick a random x velocity between -120 and 120, and a vertical one between -100 and -200 (thrusting up into the air).

To activate the emitter we call it in the hitTarget method:

hitTarget: function (bullet, target) {

    this.emitter.at(target);
    this.emitter.explode(2000, 10);

    target.kill();

    this.removeBullet(true);

}

The emitter is positioned on the center of the target Sprite and set to explode. The first parameter tells the flames to live for 2 seconds, the 2nd to explode 10 of them at once. Then we kill the target and the bullet.

This creates a suitably explosive effect:

fire in the hole

You may notice that this call to removeBullet passes a value of true. This tells the Camera tween to delay for a little longer before returning to the tank. It gives you a little more time to enjoy the effect :)

Ideas for improvements

Well, where do I start?! There are hundreds of things you could do to this game. Taking inspiration from both the original and genre evolutions like Worms you could add all manner of fun:

  • An AI opponent to shoot back at you!
  • Add a random wind effect, throwing your bullet off course
  • New weapons :) (Holy hand grenades anyone?)
  • Randomly create the landscape instead of using a PNG

Whatever you decide to do hopefully this has given you a taster and some brain food. If you evolve this further I'd love to know!

Comments

comments powered by Disqus