Advanced Flash 8 Game Programming : Pixel level collision detection Part III (or let’s start a real game)

In Part 1 – Advanced Flash 8 Game Programming : Pixel level collision detection I discussed the very basics of doing Pixel level hit detection with the bitmapData object. In Advanced Flash 8 Game Programming : Pixel level collision detection Part II (or let’s add Bitmap animation caching) I added in a simplified version of animation loop caching. In part III, we will start to make a simple game. This is what we are going to tackle:

1. We will use a play screen, created in Fireworks (or any other graphics program), as the maze or room the player is in. It will be viewed from the top. It will be a little odd looking because the Ari Feldman’s Sprite Lib GPL sprites we are going to use are viewed from the side. In any case it doesn’t look much worse than many commercial early 80’s games. This screen will be used as our wall map and we will use bitmap hitTest on the entire bitMap. This is a alternative to “tile-based” detection that has been a staple of game development for years. This isn’t to say that tile-based detection is bad or wrong, this is just a different method to accomplish the same thing. In fact, a combination of the two will be discussed in a later lesson.

2. Next we will add in animations for moving the player in 4 distinct directions and cache the animations for each. We will also add in variables for the player’s speed and an animation delay to help the player look a little more realistic.

3. We will start to create a more proper game loop with code more organized. We will also add in a Frame Rate counter, and use temp variables to hold current player position info before rendering the screen.

I will be doing a lot more explaining this time around as much has changed.

[cc lang=”javascript” width=”550″]
//hitpixels_with_bitmap_animationloop3.fla
//Jeff Fulton 2007
//www.8bitrocket.com
//pixel level collision detection with bitmap data object

import flash.display.*;
import flash.geom.*;

var keyPressed:Boolean=false;
var timeOfLastFPS:Number = 0;
var FPSCOUNT:Number = 0;
//pixel level collision detection with bitmap data object

//1. load in spriteMap from library
var spriteMapBitmap:BitmapData = BitmapData.loadBitmap(“actionMap”);
//mc.attachBitmap(spriteMapBitmap, this.getNextHighestDepth());

[/cc]

***** *****
The above section of code imports the in the internal Flash classes needed for manipulating bitmaps and the math classes needed for creating a rectangle of screen data needed from a bitmap. We are also creating variables to hold if a key has been pressed and to calculate the current frame rate.
*****
*****

[cc lang=”javascript” width=”550″]//2 create an array of bitmaps for the player sprite
//in this example, the player has two frames of animation
//this is a simple example, so we won’t do 2 frames for every direction of movement, we’ll just use these two

[cc lang=”javascript” width=”550″]
// copy two sprite images for left movement
var aPlayerLeftSpriteArray:Array=new Array();
var tempSprite:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite.copyPixels(spriteMapBitmap, new Rectangle(0, 72, 32, 32), new Point(0, 0));
aPlayerLeftSpriteArray.push(tempSprite);
var tempSprite2:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite2.copyPixels(spriteMapBitmap, new Rectangle(0, 102, 32, 32), new Point(0, 0));
aPlayerLeftSpriteArray.push(tempSprite2);

[/cc]

***** *****
Here we have created an array to hold two animation frames for the left movement of our player. We are copying these frames from our imported spritemap. Below we will do the same thing for right, up, and down movement.
*****
*****

[cc lang=”javascript” width=”550″]
// copy two sprite images for left movement
var aPlayerRightSpriteArray:Array=new Array();
var tempSprite:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite.copyPixels(spriteMapBitmap, new Rectangle(36, 72, 32, 32), new Point(0, 0));
aPlayerRightSpriteArray.push(tempSprite);
var tempSprite2:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite2.copyPixels(spriteMapBitmap, new Rectangle(36, 102, 32, 32), new Point(0, 0));
aPlayerRightSpriteArray.push(tempSprite2);

// copy two sprite images for up movement
var aPlayerUpSpriteArray:Array=new Array();
var tempSprite:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite.copyPixels(spriteMapBitmap, new Rectangle(72, 72, 32, 32), new Point(0, 0));
aPlayerUpSpriteArray.push(tempSprite);
var tempSprite2:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite2.copyPixels(spriteMapBitmap, new Rectangle(72, 102, 32, 32), new Point(0, 0));
aPlayerUpSpriteArray.push(tempSprite2);

// copy two sprite images for down movement
var aPlayerDownSpriteArray:Array=new Array();
var tempSprite:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite.copyPixels(spriteMapBitmap, new Rectangle(180, 72, 32, 32), new Point(0, 0));
aPlayerDownSpriteArray.push(tempSprite);
var tempSprite2:BitmapData=new BitmapData(32,32,true,0x00000000);
tempSprite2.copyPixels(spriteMapBitmap, new Rectangle(180, 102, 32, 32), new Point(0, 0));
aPlayerDownSpriteArray.push(tempSprite2);
[/cc]***** *****
Notice above we have created single dimensional arrays to hold each position. We easily could add all of these to another array to create one multi-dimensional array to hold all movement animation for our player. That is exactly what we will do on a later lesson.
*****
*****

//3. put player on screen, copy pixels from sprite to mc1

[cc lang="javascript" width="550"]
var playerSprite:BitmapData=new BitmapData(32,32,true,0x00000000);
//playerSprite.copyPixels(spriteMapBitmap, new Rectangle(0, 72, 32, 32), new Point(0, 0));
this.createEmptyMovieClip("player_mc",this.getNextHighestDepth());
var aPlayerSpriteArray:Array=aPlayerLeftSpriteArray;
var playerSpriteIndex:Number=0;
player_mc.attachBitmap(aPlayerSpriteArray[0],100);
playerSprite=aPlayerSpriteArray[playerSpriteIndex];
player_mc._x=32;
player_mc._y=32;
player_mc.tempx=32;
player_mc.tempy=32;
player_mc.speed=3; //how many pixels to move with each key press
player_mc.delay=5; //how many keypresses to wait until moviing to the next animation frame
player_mc.delayCount=0;
[/cc]

***** *****
Now, here is where we start to add some meat. We create a bitmap object (playerSprite) that will hold a reference to the current bitmap data object that is attached to put player movieClip. We do this because once we have attached a bitmap to a clip, there is no way to access it directly. We need to access it for hit detection. Also we add in some new variables called tempx, tempy, speed, delay, and delayCount. Tempx and tempy will be used to hold the position our player WILL be at in the next interval. We will test this position in our hitTest rather than moving the player then testing. This is the start of our more organized game loop design. The speed variable is the number of pixels the player will move with each key press. The delay variable is the number of key presses that need to pass before we jump to the next animation frame for our player. The combination of these two will let you refine the player animation and allow things like speed power ups or traps where the player's speed is slowed. The delayCount is used to keep a count of the number of key presses that have passed since the last animation frame.
*****
*****

[cc lang="javascript" width="550"]//4. create a walls sprite
var screen2:BitmapData = BitmapData.loadBitmap("screen2");
var sprite2:BitmapData=new BitmapData(400,400,true,0x00000000);
sprite2.copyPixels(screen2, new Rectangle(0, 0, 400, 400), new Point(0, 0));
this.createEmptyMovieClip("sprite2_mc",this.getNextHighestDepth());
sprite2_mc.attachBitmap(sprite2,100);
sprite2_mc._x=0;
sprite2_mc._y=0;
[/cc]

***** *****
In the above code snippet, we load a 400 x 400 bitmap into a holder clip and then place the clip on the screen. This bitmap contains the walls for our game room. The bitmap has a transparent background and we will use pixel level hit detection to determine a hit on any wall.
*****
*****

[cc lang="javascript" width="550"]

this.onEnterFrame=function() {

getKeys();
checkCurrentFrameRate();
if (keyPressed) {
checkCollisions();
render();
keyPressed=false;

}

}
[/cc]

***** *****
The onEnterFrame is now our simplified game loop. We new are separating our loop into very distinct functions. First we check for key presses. Next we calculate and display the current frame Rate. If a key was pressed, then we check for collisions between the player and the walls and render the player back to the screen. If we were to have other objects on the screen such as collectibles and enemy sprites, our game loop would need to be organized differently. We'll get to that in a later lesson.
*****
*****

[cc lang="javascript" width="550"]
function render() {

player_mc.delayCount++;

if (player_mc.delayCount > player_mc.delay) {
player_mc.delayCount=0;
playerSpriteIndex++;
if (playerSpriteIndex > aPlayerSpriteArray.length-1) playerSpriteIndex=0;
player_mc.attachBitmap(aPlayerSpriteArray[playerSpriteIndex],100);
playerSprite=aPlayerSpriteArray[playerSpriteIndex];
sprite_txt.text=playerSpriteIndex;
}
//trace("player_mc.tempx=" + player_mc.tempx);
//trace("player_mc.tempy=" + player_mc.tempy);
player_mc._x=player_mc.tempx;
player_mc._y=player_mc.tempy;

}

[/cc]
***** *****
The render function role is to update the player on the screen. It first checks to see if it needs to update the player animation loop (based on the delayCount varbaible), and then it changes the player's position to represent the next calculated position.
*****
*****

[cc lang="javascript" width="550"]function checkCurrentFrameRate():Void {
if (timeOfLastFPS+1000<getTimer()) {
frame_txt.text = FPSCOUNT;
timeOfLastFPS = getTimer();
FPSCOUNT = 0;
} else {
FPSCOUNT++;
}
}
[/cc]

***** *****
The checkCurrentFrameRate function uses a getTimer call to check for the number of milliseconds that have passed since the last frame. If it has been 1000 or more (1 second = 1000 milliseconds) then we reset the number of frames counted. If not, it adds 1 to the frame rate counter. This is a pretty accurate way to calculate the frame rate and it will be within 1 or 2 frames of being correct.
*****
*****
[cc lang="javascript" width="550"
]function getKeys() {
if (Key.isDown(Key.UP)) {
player_mc.tempy-=player_mc.speed;
aPlayerSpriteArray=aPlayerUpSpriteArray
keyPressed=true;
}
if (Key.isDown(Key.DOWN)) {
player_mc.tempy+=player_mc.speed;
aPlayerSpriteArray=aPlayerDownSpriteArray;
keyPressed=true;
}
if (Key.isDown(Key.LEFT)) {
player_mc.tempx-=player_mc.speed;
aPlayerSpriteArray=aPlayerLeftSpriteArray;
keyPressed=true;
}
if (Key.isDown(Key.RIGHT)) {
player_mc.tempx+=player_mc.speed;
aPlayerSpriteArray=aPlayerRightSpriteArray;
keyPressed=true;
}

}
[/cc]
***** *****
The getKeys function has been modified from the previous 2 versions. First, instead of automatically changing the player's _x and _y right away, we store the next values to temp variables. Also, we make sure to change to aPlayerSpriteArray to reference the array for the direction the player if headed. This code is very simple but works well here because each direction contains only two animation frames.
*****
*****

[cc lang="javascript" width="550"]

function checkCollisions() {

var myPoint:Point = new Point (sprite2_mc._x, sprite2_mc._y);
if (playerSprite.hitTest (new Point (player_mc.tempx , player_mc.tempy), 255, sprite2, myPoint, 255)){
bitmap_txt.text="true";
//trace("bitMap hit detected");
player_mc.tempx=player_mc._x;
player_mc.tempy=player_mc._y;
}else{
bitmap_txt.text="false";
}

}

[/cc]

***** *****
Our collision detection function has been modified to look almost nothing like the previous two. We have done away with the movieClip hitTest from before because the player is ALWAYS on the room bitMap (sprite2). Because of this, the hitTest would ways be true and there is no reason to waste time doing it. Also, we have changed the point we check in the bitmapData.hitTest call to reference the NEXT point for the player. If a hit is detected, we resent tempX and tempY to be the original values before the render function is called. This stops the player from moving into an area that he/she is not supposed to occupy.
*****
*****

That's it for this time. Check out the .fla and example below for more info.

/downloads/blog/hitpixels_with_bitmap_anmationloop3.swf

hitpixels_with_bitmap_anmationloop.swf (2007)
A demo of using bitmapData to do pixel level collision detection and bitmap animation caching.

Use the arrow keys to move the spaceman around the room.

The bitmap hit will become true only if the spaceman collides with a walls (green), even through he is actually colliding with the wall bitmap all the time.

The Player Sprite number in the text box represents the current sprite in the array (bitmapData object).

The frame rate tells the current frame rate.

download the fla file

Ari Feldman's Sprite Lib used via GPL

Leave a Reply