In creating my Space Invaders clone, one aspect of the game that I found particularly difficult to implement was the "dissolving" shields (documented here). I recently found this fantastic tutorial by Richard Davey here titled "Creating a game like Tanks / Worms", which provided the answer to my problems in the form of Phaser objects and methods to manipulate HTML canvas elements.
As with the rest of the Phaser Coding Tips series, it is in Phaser 2. Also, as someone who started learning about game programming straight from Javascript/Phaser (i.e. no knowledge of HTML or "drawing" to Canvas element), it did take me a bit of time to figure out what was going on - this post is mostly about my meanderings before arriving eventually to the converted Phaser 3 code.
As with the other Phaser Coding Tips, the original tutorial covers many important techniques for creating games, in a highly concise manner.
Challenges of implementing Space Invader "dissolving" shields
The original Space Invaders arcade game ran on a black and white screen where each pixel was represented by one bit in the video memory; a "white" pixel was represtend by the relevant bit set to 1 and a "blank" (or black) pixel was represented by the bit set to 0. Collision detection was simply checking to see if a particular pixel was 1 or 0.
As far as the "erasing" part of the shield, you simply "deleted" relevant pixels of the shield, in the explosion pattern. The video memory itself contained the latest state of the shield. When I originally wrote my Space Invaders code, I ended up maintaining the state of the shield as an 22x16 array of 1's and 0's (each shield being 22x16 pixels in size), writing methods to draw the shield, erase bits of the shield as well as a "custom collision detection routine" - it did achieve the desired effect but with extremely long-winded code. All I really wanted to do was to "draw" the shield on the screen, then as the bullet flew across the screen, simply check whether the pixel was green or not, if green, "splatter" an explosion pattern at the position of the bullet (or rather, delete the bits of the shield in the form of the explosion pattern).
The tutorial example game does exactly that - as the "bullet" fired from the tank flies across the sky, the "pixel" at the bullet position is checked, and if not blank, a "circle" is drawn on top of land, creating the effect of blowing a hole in the land.
The relevant part is actually described in part 2 of the tutorial article as opposed to part 1 - part 2 of the article is available here.
Drawing to Canvas element using normal Canvas context operations
Phaser 2 version
The key paragraphs from the original tutorial are:
The land is a BitmapData object to which we draw our land.png file. This PNG has the landscape drawn on a transparent background....
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).
Official Phaser 2 documentation describes BitmapData as follows.
A BitmapData object contains a Canvas element to which you can draw anything you like via normal Canvas context operations. A single BitmapData can be used as the texture for one or many Images / Sprites. So if you need to dynamically create a Sprite texture then they are a good choice.
Yes! That is exactly what I want to do - dynamically create a sprite texture, as the shields are hit!
Phaser 3's CanvasTexture class
Having sifted through Phaser 3 documentation, it looks like the equivalent operations can be achieved by using something called Phaser.Textures.CanvasTexture, which is a special kind of Texture that is backed by an HTML Canvas Element as its source.
By staring at the various Phaser site examples, I came up with the following to draw the land.png image to the canvas. The last line is not directly to do with initially drawing the land, but is critical when we come to draw the "holes" in the land upon bullet collision.
// you access the Texture manager (a singleton class) via scene.textures
// createCanvas creates a new Textures using a blank Canvas element of the size given
this.canvas = this.textures.createCanvas('canvastexture', 992, 480);
// get(key) returns the Texture with the given key
// getSourceImage returns the source image the Texture Manager uses to render with
this.land = this.textures.get('land').getSourceImage();
// draw(x,y,source) draws the given image or Canvas element to this canvas texture
this.canvas.draw(0, 0, this.land);
// Now, display the Canvas Texture by adding it to an Image of the Phaser scene
this.add.image(0, 0, 'canvastexture').setOrigin(0);
// the globalCompositeOperation property sets or returns how a source image is dawn onto a destination iamge
// 'destination-out' basically means the part of the destination image drawn over by the source image will be transparent
this.canvas.context.globalCompositeOperation = 'destination-out';
// Now anything drawn to the canvas will use this op
Pixel-by-Pixel Collision Detection
Phaser 3 provides the same getPixel method as Phaser 2's bitmapData equivalent. This returns an object with the red, green, blue and alpha values set in the r, g, b and a properties. In the tutorial code example, collision detection is performed simply by seeing whether the alpha value is greater than 0 (ie not transparent).
var x = Math.floor(this.bullet.x);
var y = Math.floor(this.bullet.y);
var rgba = this.canvas.getPixel(x, y);
if (rgba.a > 0) {
Drawing "hole" in the land
Phaser 2 code
The original Phaser 2 code to explode "holes" in the ground is as follows. The first line in blue is Phaser 2's bitmapData built-in method that sets the "blend mode" to 'destination-out'. The 2nd line is bitmapData method to draw a circle to the canvas. The 3rd line resets the blend mode (effectively sets it to 'source-over'). The final line re-creates the BitmapData.imageData from the current context (ie the land with the hole dug out of it).
this.land.blendDestinationOut();
this.land.circle(x, y, 16, 'rgba(0, 0, 0, 255');
this.land.blendReset();
this.land.update();
Phaser 3 equivalent
As far as I can tell, Phaser 3's CanvasTexture class does not provide a built-in method for drawing circles, like in Phaser 2. However, as described by the official documentation, you can "use the properties of this texture to draw to the canvas element directly, using all of the standard canvas operations available in the browser". Specifically you can do this via the canvas property, and using beginPath(), arc, and fill to draw a filled circle. The final line does exactly the same as the Phaser 2's method of the same name.
this.canvas.context.beginPath();
this.canvas.context.arc(x, y, 16, 0, Math.PI*2);
this.canvas.context.fill();
this.canvas.update();
And that recreates the original code's painting of the land, and bullets blowing up the land I believe. There's plenty more to learn from in the original tutorial like panning the cameras and creating big explosions using particle emitters. I have tried to faithfully convert all the features to Phaser 3 so you may like to read the original tutorial while looking at the Phaser 3 conversion.
Other bits that might be relevant
Since this is an experiment to understand drawing to Canvas, I have specified game.config.type as Phaser.CANVAS. Typically I do not specify this property, thereby leaving it to the default Phaser.AUTO, which leaves it up to Phaser to choose which renderer to use (ie it would pick WEBGL if available).
Here is the Codepen of the Phaser 3 translation.
Space Invaders Dissolving Shields
Armed with the above knowledge, creating the "dissolving" Space Invaders shield is easy. First draw, the 4 shields with the following lines (the last 2 lines are not directly to do with drawing the shields, but required to create the effect of erasing bits of the shield) :
this.canvas = this.textures.createCanvas('canvastexture', 224, 256);
this.shield = this.textures.get('shield').getSourceImage();
// draw 4 shields to canvas
for (let i = 0; i < 4; i++) {
this.canvas.draw(35 + 45 * i, 185, this.shield);
};
this.add.image(0, 0, 'canvastexture').setOrigin(0);
this.canvas.context.globalCompositeOperation = 'destination-out';
// create the bomb image explosion texture
this.bombExplosion =
this.textures.get('bombExplode').getSourceImage();
Then, the code to check bullet's collision with a shield and erasing bits of the shield is as follows. Strictly speaking, I should probably check the pixel data at all the 4 pixels occupied by the bullet as opposed to the tip of the bullet, but this does the job.
bulletVsShield() {
const x = Math.floor(this.bullet.x);
const y = Math.floor(this.bullet.y-2);
const rgba = this.canvas.getPixel(x, y);
if (rgba.a > 0) {
this.bullet.setStatus(Bullet.Status.STANDBY);
this.canvas.draw(x, y-4, this.bombExplosion);
this.canvas.update();
}
}
Which creates the following type of effect.
The Codepen, should you be interested, is accessible below.
Having created dissolving shields...
So with this "technique" I was able to achieve in a few minutes what took me hours to code originally. Manipulating the canvas at pixel level is so intuitively easy to understand and in some respects, very efficient. It is perfect for recreating the effects in the very early arcade games that used bit-by-bit (pixel-by-pixel) collision detection, like Space Invaders. So much so, that in my next post I explain how I "simulated" the barrels rolling along sloping platforms in the classic Donkey Kong game.
Comments