8bitrocket.com
12Sep/070

Flash CS3: Actionscript 3 (AS3) Game Primer #3: Bitmap Collision Detection

Flash CS3: Actionscript 3 (AS3) Game Primer #3: Bitmap Collision detection

One of the great features of Flash AS2 and AS3 is the ability to use the non-transparent colored pixels in a bitmapData object to accomplish pixel level hit detection. In this tutorial, we will build upon the last last two lessons : Flash CS3: Actionscript 3 (AS3) Game Primer #2: Asynchronous key detection for arcade games and Flash CS3: Actionscript 3 (AS3) Game Primer #1: Tile Maps, XML, and bitmapData to create a relatively simple but powerful example.

In the Game Primer #2, I discussed building some rudimentary code to re-create the key.isDown functionality that was removed in AS3. Since that tutorial was published, I have been informed that Flashgamecode.net has released a very slick key polling class that does this job very elegantly. We'll explore using this class, as well as build on the tile map and xml code we created in Primer #1.

The Tile Map
I created I unique set of simple tiles and xml map for this example. They were created using the same process as in the Flash CS3: Actionscript 3 (AS3) Game Primer #1: Tile Maps, XML, and bitmapData tutorial.

The tile set looks like this:

This is a 320x64 set of 20 32x32 tiles. I am only using 13 of the 20 tiles in this example. 14 actually, as #14 is a blank tile.

I used Mappy to create this tile map from the the above tile set.:

jpg example of the tile map created in Mappy.

This is a relatively simple map on a 10 x 10 grid. The red + is the player in this instance. As you can see, I have purposely created some tiles that do not take up the entire tile square and also ones that are odd (at least not square) shapes. Normally, if you did a pure tile based hit detection, you would be forced to accept a "hit" when the player enters the tile with a blue wall or object. The example below demonstrates this:

User arrow keys to move the player (red +) around the screen.

Basically, if the player's nextx and nexty values (the center of the +) are in a tile that is not blank, then a hit is detected and the player cannot move. Notice how inaccurate this test is. Some false positives show hits, and some true hits are not detected. It makes for an ugly experience. If you decide to make all tiles perfect squares, then this type of hit detection might work fine. In the case above, this really is not the ideal method to detect collisions between the player and the walls. We could build on this tile-based detection by adding more hit spots to check (we check the origin of the tile right now only). We could check the outer most point on the four points of the +, and that might actually do the job. Instead, we are going to try something a little different.

The next example is a more ideal method to detect these collisions:

User arrow keys to move the player (red +) around the screen.

In this example you will notice that the collisions are pretty much pixel perfect. Any transparent area on the player or a wall will not be used in the collision test. For this example, the entire wall and obstacle structure was read from the xml, copied from the tile set, and placed in a bitmapData object. When the user presses a key to move the player, the player is not immediately moved. The nextx and nexty variables are updated to reflect where the player will be in the future if this move is valid. The collision test is fired off based on these new values. If the next position is a valid one, the move is allowed. The collision is based on the ENTIRE bitmapData object (non-transparent areas) for the walls, and just the non-transparent bitmapData of the player. The entire code for this is below:

[cc lang="javascript" width="550"]/**
* ...
* @author Jeff Fulton
* @version 0.1
*/

package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.geom.*;
import flash.display.BitmapData;
import flash.net.URLRequest;
import flash.events.*;
import flash.net.*;
import flash.display.Loader;
import flash.ui.Keyboard;
import fgc.input.KeyPoll;
//http://flashgamecode.net/free/key-polling-class

public class PixelHitTestDemo extends MovieClip {
private var tilesBitmapLoader:Loader;
//private var tilesBitmap:Bitmap;
private var loader:Loader; // The bitmap loader
private var mazeData:XML;
private var xmlLoader:URLLoader;
private var aTileMap:Array=new Array();
private var mapRows:int=10;
private var mapCols:int=10;
private var screenBitmapData:BitmapData;
private var screenBitmap:Bitmap;
private var tileMapWidth:int=320;
private var tileMapHeight:int=64;
private var tileSize:int=32;
private var screenWidth:int=320;
private var screenHeight:int=320;
private var tilesBitmapData:BitmapData;
private var player:MovieClip=new MovieClip();
private var playerBitmap:Bitmap;
private var tilesPerColumn:int;
private var key:KeyPoll;

public function PixelHitTestDemo() {
//load in tile sheet image
loader = new Loader( );
loader.contentLoaderInfo.addEventListener(Event.INIT,tilesLoadInit);
loader.load(new URLRequest("primer3_tiles.png"));

}

private function tilesLoadInit (e:Event):void {

tilesBitmapData=Bitmap(loader.content).bitmapData;
//load in xml file for map
loadMapXML();
}

private function loadMapXML():void {
xmlLoader=new URLLoader();
xmlLoader.addEventListener(Event.COMPLETE, xmlLoadComplete);
xmlLoader.load(new URLRequest("primer3_maze.xml"));
}

private function xmlLoadComplete(e:Event):void {
mazeData=new XML(xmlLoader.data);

//loop through xml and add rows and columns to aTileMap array
//var rowXmlList:XMLList=mazeData.ROWX;
for (var rowCtr=0;rowCtr

This is a lot of code, and much of it (the xml load and the tilesheet.png load and creation) was discussed in Game primer #1. For a simple review, here is a brief discussion of what the methods do:

public function PixelHitTestDemo()
This is the constructor. In this relatively simple example, its job is to load in the primer3_tiles.png tile map, and create a listener for the INIT event on the load.

private function tilesLoadInit (e:Event):void
This function is fired off when the tile png file is loaded. It then assigns a bitmapData object to hold the tile set.

private function loadMapXML()
This method begins the xml load of the map data. It creates an event listener (xmlLoadComplete) that is fired off when the data has been loaded.

private function xmlLoadComplete(e:Event)
This method does some real work. First it assigns the loaded in data to an xml object (mazeData) then it loops through the tilerow and tilecol xml nodes to assign a value to the aTileMap array. This array holds the 2d row and column single digit tile sheet location for each tile on the final maze to be displayed.

Next it loops through the aTileMap array using the tile set id number assigned above to find the tile in the tile set png file and copy it to to correct location in the screenBitmapData object. After the screenBitmapData has been created, it is assigned to the bitmapData property of the screenBitmap display object through the constructor - screenBitmap=new Bitmap(screenBitmapData). It is then added to the screen. This is a far as we got in Primer #1. We will now create a player object, a game loop, check for user input, check for collisions, and update the screen.

function createPlayerSprite():void
This method creates a player movieClip using the tile on the tile set as the surface bitmapData: playerBitmapData.copyPixels(tilesBitmapData,new Rectangle(64,32,tileSize,tileSize),new Point(0,0));
This data will be used in the collision detection with the screenBitmapData created in the method above. I have used a moveclip here only as a simple example. The main reason I did so is that is it the only display object that is a dynamic class. For this simple tutorial, I didn't want to create a separate player class. If I had, the player would be a Sprite because no timeline is needed. Using a movieclip as a display object is a quick and dirty method to prototype the types of attributes I would later create in my Player class.

This method also creates an ENTER_FRAME event that will call the "run" method (our basic game loop) repeatedly. Also, an instance of the Flashgamecode.net KeyPoll class is created to easily replicate the deprecated (and removed) key.isDown() method from AS2.

addEventListener(Event.ENTER_FRAME, run);
key = new KeyPoll( stage );

function run(e:Event):void {
getKeys();
checkCollisions();
renderScreen();
}

The run method does a lot of work, even though it looks rather simple. Basically it collects key input, then checks to see if the key input will cause a collision in the future, and then it renders the screen based on the result of the collision. Without this method, there would be no game.

function renderScreen():void
This method merely copies the nextx and nexty values to the x and y values of the player.

function getKeys()
Using the KeyPoll class, this method updates the nextx and nexty values by adding the player.speed variable where appropriate.

function checkCollisions()
This is the meat of the collision detection and it is pretty simple at first glance, but don't be deceived, a lot is going on here. We'll take the one call in little bites to describe what is going on. Here is the hitTest call in its entirety.

if (playerBitmap.bitmapData.hitTest(new Point(player.nextx-player.bitmapHitOffset,player.nexty-player.bitmapHitOffset),255,screenBitmap.bitmapData,new Point(screenBitmap.x,screenBitmap.y))) {

if (playerBitmap.bitmapData.hitTest - Here we are using the bitmapData stored in the player object and calling the hitTest method of the bitmapData object. Don't be confused, just because I used a movieClip instance to hold the player attributes, I am not doing a hitTest on the movieClip, I am doing it on the bitmapData inside the clip.

(new Point(player.nextx-player.bitmapHitOffset,player.nexty-player.bitmapHitOffset) - The first parameter in the bitmapData.hitTest method is a Point object representing the upper left-hand corner of the object being tested. In this case that object is the player and the point is the player.nextx and player.nexty values - meaning where that point WIIL BE, not where is currently is on the screen. Also, we subtract a "bitmapHitOffset" from each because I placed the player at an origin point of (-.5*width) for x and (-.5*height) for y. That put the players x and y value in the middle of the red +.

,255 - This represents the "alphaThreshold" for the test. That is the highest alpha channel value that is considered opaque for this hit test. In this case I want no transparency to detect a hit, so the number is 255.

,screenBitmap.bitmapData,new Point(screenBitmap.x,screenBitmap.y))) { - This is the second object for the collision detection. The entire screen of screenBitmap data, and a point representing the top left corner of the object (0,0).

player.nextx=player.x;
player.nexty=player.y;

If a hit is detected, then the nextx and nexty y values are reset back to the original values. This way, when the renderScreen() method fires, the player will not move. I am sure there are more sophisticated methods for do this, such as setting a boolean to true if the move is to happen, etc. I chose the simplest version for the tutorial.

That's it for this time. You can download all of the files for the tutorial here.

If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.

This site is protected by Comment SPAM Wiper.