Last time, I added coarse "box" collision to the game. Box collision is fast, but it's not accurate enough for a shoot-em-up. What I need is pixel perfect collision. This works using "collision maps" for the two objects. A collision map is basically the same as a sprite image (which is sometimes called a pixel map), but instead of storing a colour value for each pixel, I store a true or false value - true if the pixel is set, false if not. When it comes to checking for collision, I walk across the two collision maps and check for any pixel where both maps are set to true. If that happens then the two objects are colliding.
In the following image, you can see the difference between the two types of collision:
If we were to just consider box collision, then it looks like the bullet has hit the player object - you will have noticed that in the last step where the collision felt really unfair. But if we compare the actual pixels of the two images for the two objects then we can see that there is no collision. From a conceptual point of view this is pretty straight-forward, but one extra thing that you'll see in shoot-em-ups is that the image you see on screen is not the image that is used for the collision map. The maps used for collision are usually much smaller than the sprites themselves, and this is what makes it possible for a skilled player to slip through holes that seem far too small to fit through.
In the case of our player, the collision map is just the size of their ship's cockpit - this is a pretty common trick in shoot-em-ups. The bullet collision map is also slightly smaller than the sprite that it represents. Here's another image to show the difference between what you see and what the game uses for collision tests:
In the following image, you can see the difference between the two types of collision:
If we were to just consider box collision, then it looks like the bullet has hit the player object - you will have noticed that in the last step where the collision felt really unfair. But if we compare the actual pixels of the two images for the two objects then we can see that there is no collision. From a conceptual point of view this is pretty straight-forward, but one extra thing that you'll see in shoot-em-ups is that the image you see on screen is not the image that is used for the collision map. The maps used for collision are usually much smaller than the sprites themselves, and this is what makes it possible for a skilled player to slip through holes that seem far too small to fit through.
In the case of our player, the collision map is just the size of their ship's cockpit - this is a pretty common trick in shoot-em-ups. The bullet collision map is also slightly smaller than the sprite that it represents. Here's another image to show the difference between what you see and what the game uses for collision tests:
Onto The Code
So how does this work in code? I only needed to add write two new chunks of code: one function to generate collision maps from the source images, and then an update to the isColliding() routine.
First up, here's the generation code. This code takes a source image and draws it to a temporary Canvas object. From there I can grab the pixels (in Red, Green, Blue, Alpha format) that make up the sprite. This happens on line 8, where the pixel data is grabbed into a linear array of bytes. The first four bytes are the RGBA values of the first pixel, then the next four bytes for the next pixel, etc.. I step through this array, one pixel at a time, and check the alpha value. If the pixel is transparent then I consider that to be a false value in the collision map, otherwise it's a true value. It follows that the collision map is defined by any part of the source image that is completely transparent. Finally, I return an object which contains the width, height and collision map data.
Secondly, here are the changes to the isColliding0 function:
You'll see a couple of loops in there where I walk across the collision maps of one of the objects. For each bit of the mask I then figure out the overlapping part of the second object's collision map. Finally, if both parts of both maps are true then it means that collision has happened.
The code is in BitBucket and the playable online version is here.
You'll see a couple of loops in there where I walk across the collision maps of one of the objects. For each bit of the mask I then figure out the overlapping part of the second object's collision map. Finally, if both parts of both maps are true then it means that collision has happened.
Plumbing
There are a couple of other small bits of plumbing required to get this all working, the first one being the creation of the collision maps in init():
..and then I need to store those maps in the objects, so I added a new argument to the base object constructor:
Wrapping Up
I talked about the need for pixel perfect collision, why the collision maps are not what they seem, and then implemented some collision routines that are based on sprite images. This creates a flexible and accurate collision system that can be used for all object types.The code is in BitBucket and the playable online version is here.
Comments
Post a Comment