I never thought development of web games can be that simple until I started development of my first game with Adobe Flex. I made two games, without any prior experience with Action script, in only ten months: "Fitter" and "RGB".

I found many answers to my questions on different forums and web sites, and it is time to share my knowledge with Flex community.

Wednesday, March 17, 2010

Per-pixel collision

In previous chapter we implemented tiled background and now the background image does not repeat as much as it did in older versions. But there is still something missing: it is just an image. Nothing happens when snakes runs into the rock and eggs can float over the pit just like it wasn't there.

We have to implement collision detection again, but none of the methods that we have learned in chapter 19, will be of much use here. We have to somehow distinguish between the dangerous and non-dangerous parts of each tile, before we can detect collision.

There are many was to do this. We could write the list of tiles that will trigger the death of the snake, whenever it enters the area, where tile is displayed. This would be quite simple to implement, but not precise enough. If we could provide the same information for small chunks of the tile, let's say 4x4, precision would be very much increased, but the whole process would require to much work. We will use the approach, that is the most precise and does not require to much extra work.



Per-pixel collision

To implement per-pixel collision, we first need information, which pixels in the tile set can trigger the collision and which can't. This information will be stored in the copy of the original image, where only pixels that matter, are visible. The rest of the pixels are transparent.

Bellow is the image that will be used in this chapter, to detect collision. If you download project package for this chapter, you can find the image inside Images folder, under the name Background.png.


I am making tiles for the game with image editor, that supports layers (I guess all good image editors supports them). To create such an image, I disable all layers, that don't matter, and remove colour information to decrease the size of the file.

Background.as

Image file has to be embedded, before it can be used:

[Embed(source="../Images/Background.png")] private var collisionsImg:Class;

We need additional BitmapData object, where pixels from embedded image will be copied:

private var bitmapCollision:BitmapData = null;

Function checkCollision works similar to draw function, but instead of copying pixels from Background.jpg into screen buffer, it checks if the pixels on the same location, but from different image - Background.png, are transparent or not.

public function checkCollision(bounds:Rectangle):Boolean
{
  if (bitmapCollision == null)
  {
    bitmapCollision = new BitmapData(300,600,true,0x00000000);
    bitmapCollision.draw(new collisionsImg());
  }

  for (var y:int = bounds.y; y > bounds.y - bounds.height; y--)
    for (var x:int = bounds.x; x < bounds.x + bounds.width; x++)
    {
      var tileX:int = x / TILE_SIZE;
      var tileY:int = y / TILE_SIZE;

      if ((tileX < 0) || (tileX > 9) || (tileY < 0))
        return true;

      var cells:Array = rows[tileY % rows.length];
      var tile:Point = cells[tileX];

      var pix:uint = bitmapCollision.getPixel32(x % TILE_SIZE + (tile.x - 1) * TILE_DIST, tile.y * TILE_DIST - y % TILE_SIZE);

      if ((pix >> 24 & 0xFF) > 0)
        return true;
    }

  return false;
}

Data from embedded image is copied into BitmapData object, but only the first time the function is called. The code inside for loops, scans the entire area of the game world, that is specified within bounds parameter. Area is checked pixel by pixel and within each iteration, game world coordinates are transformed into tile coordinates and vertical and horizontal index of the tile are obtained from rows Array.

Function getPixel32 in BitmapData class, returns ARGB colour value of the pixel on specified location. Returned value is uint (unsigned integer) which is 32-bit numeric value: 8 bits for alpha channel, 8 bits for red, green and blue colour. There are only first 8 bits, that we are interested in. If all those bits equal to zero, pixel is fully transparent and collision is not detected; the search continues. If value is greater than zero, pixel is not transparent, collision is detected and function immediately exits and returns true.

Now that we have a tool to detect collision, we can use it.

Game.as

First, we will update CheckCollision function. You can append this statement to the end of the function:

//check snake and the background
if (background.checkCollision(headBounds))
{
  snake.kill();
  return snake;
}

It cannot be more simple than that: headBounds object represents the area in the game world, where head of the snake is currently position. This is the area that checkCollision function in Background class will have to check. When function returns true, the head of the snake is positioned over at least one pixel, that is non-transparent in the copy of background image, that is used for collision detection.

If you recompile the game and play it, the snake will die whenever it hits the rocks. The problem is, that eggs are sometimes randomly positioned on the rocks and they cannot be eaten by the snake. We have to update CreateRandomEgg function, to fix this problem.

Replace the return statement at the end of the CreateRandomEgg function, with:

var newEgg:Egg = new Egg(x,y,initial);

//check the background
if (background.checkCollision(newEgg.getBounds2(20)))
  return CreateRandomEgg(initial);

return newEgg;

The previous version of CreateRandomEgg function, made new egg on random location in the game world, within the visible area of the player If the egg was positioned to close to the snakes body, function was called again, until correct location was not found.

The new version works the same, but it also makes sure that new egg is not positioned on the rock, over the pit... In fact it checks the area, that is much bigger than the area, which is returned by getBounds function from Egg class. New egg is generated at least 25 pixels away from nearest dangerous area. This area is returned by new function in Egg class - getBounds2. The implementation of this function has to be added to Egg.as file:

public function getBounds2(margin:int):Rectangle
{
  return new Rectangle(position.x - 5 - margin, position.y + 9 + margin, 11 + 2 * margin, 16 + 2 * margin);
}

No comments:

Post a Comment