Tutorial: N-way tile-based blit fine scrolling in AS3 (part 3)
This is a long tutorial, so it has been split into 3 parts:
In this tutorial we will discuss the theory and practice behind 360 degree n-way blit fine scrolling in AS3. What does that mean? N-way scrolling is a means by which you can scroll the screen in any direction based on the angle the main character of the game is facing. Part 3 demonstrates 4-way and 8-way techniques and provide some auxiliary methods and functions that I have used in creating these demos.
Part 1 explains the concepts of N-way blit scrolling
Part 2 adds a car and and let you drive it around the screen
Part 3 (this) demonstrates 4-way and 8-way techniques and provide some auxiliary methods and functions that I have used in creating these demos.
How to do a 4-way scroll with the same concept.
The only thing that changes for for a 4-way scrolling world is how we handle key presses and translate them into carDX and carDY values. First, we need to change what happens when the player pressed the up, down, left and right keys AND we need to change how the viewXOffset and viewYOffset are update to deal with the new key pressed. That is basically all we need to do.
[cc lang=”javascript” width=”550″]
private function checkKeys():void {
if (aKeyPress[38]){
trace(“up pressed”);
carRotation=-90;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[40]){
trace(“down pressed”);
carRotation=90;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[37]){
trace(“left pressed”);
carRotation=-180
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[39]){
trace(“right pressed”);
carRotation=0;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
}
[/cc]
In this method, we change the carRotation to a pre-defined direction and then adjust the car speed. This will not be an accurate model for acceleration because as soon as you accelerate to the carMaxSpeed, there is no way to slow down, but it’s just a demo. In a full game, I would store the last direction pressed and if it is not the same as the new direction, I would start the carSpeed back at 0.
Here is an example of 4-way movement running:
/downloads/blog/nway_scrolling/superbugbasic_4way.swf
8-way scrolling example
8-way scrolling is not that much different that 4-way, except you will need to find some suitable input keys. In this example we will use the
QWE
ASD
ZXC
keys do all of the work. We will use the “S” to stop the car in this example.
Here are the changes needed to the checkKey() method:
[cc lang=”javascript” width=”550″]
private function checkKeys():void {
if (aKeyPress[81]){
trace(“Q pressed”);
carRotation=-135;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[87]){
trace(“W pressed”);
carRotation=-90;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[69]){
trace(“E pressed”);
carRotation=-45
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[65]){
trace(“A pressed”);
carRotation=-180;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[83]){
trace(“S pressed”);
carSpeed=0;
}
if (aKeyPress[68]){
trace(“D pressed”);
carRotation=-0;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[90]){
trace(“Z pressed”);
carRotation=135;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[88]){
trace(“X pressed”);
carRotation=90;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
if (aKeyPress[67]){
trace(“C pressed”);
carRotation=45;
carSpeed+=carAcceleration;
if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
}
}
[/cc]
This is a little ugly, and can probably be handled in a much cleaner manner, but for demonstration purposes, I just want to show how easy it is to modify the scrolling engine to work with a variety of game types. Here is this example running:
/downloads/blog/nway_scrolling/superbugbasic_8way.swf
The rest of the code
That’s it for the explanation and the examples. What follows are some of the methods in the code that I use to tie the whole package together.
You certainly do not have to use these, and I only present them to paint a complete story. I would much rather you took the code above, looked at the examples and created your own engine.
run Game()
This function is called via a timer and is the main loop. This blog entry explains exactly what the timer does and how it works. It is optimized to display at consistent frame rate across browsers and other flash player mediums,
[cc lang=”javascript” width=”550″]
private function runGame(e:TimerEvent) {
//trace(“running game”);
_beforeTime = getTimer();
_overSleepTime = (_beforeTime – _afterTime) – _sleepTime;
checkKeys();
updatePlayer();
//checkCollisions();
canvasBD.lock();
drawBackground();
drawView();
drawPlayer();
canvasBD.unlock();
_afterTime = getTimer();
_timeDiff = _afterTime – _beforeTime;
_sleepTime = (_period – _timeDiff) – _overSleepTime;
if (_sleepTime _period) {
checkKeys();
updatePlayer();
checkCollisions();
_excess -= _period;
}
//frameTimer.countFrames();
//frameTimer.render();
e.updateAfterEvent();
}
[/cc]
setUpWorld()
This function takes the xml map data and puts it into the aWorld 2d array.
[cc lang=”javascript” width=”550″]
private function setupWorld():void {
//parse xmlMapData and create aWorld array
for (var rowCtr:int=0;rowCtr&kt;worldRows;rowCtr++) {
var tempArray:Array=new Array();
for (var colCtr:int=0;colCtr<worldCols;colCtr++) {
tempArray.push(XMLLevelData.tilerow[rowCtr].tilecol[colCtr])
}
aWorld.push(tempArray);
}
//trace("row0,col0=" + aWorld[0][0]);
}
[/cc]
init()
Init simply set’s everything up and is run one time. I have code to show the mPanel on the screen in this, which is simply a MovieClip in the library that is added to the display list and shows the offsets and current tile that is the upper left-hand corner of the window. It is not needed for your game engine.
[cc lang=”javascript” width=”550″]
private function init():void {
viewWidth=240;
viewHeight=320;
mapTileWidth=16;
mapTileHeight=16;
worldCols=100;
worldRows=100;
worldWidth=worldCols*mapTileWidth;
worldHeight=worldRows*mapTileHeight;
viewCols=viewWidth/mapTileWidth;
viewRows=viewHeight/mapTileHeight;
viewXOffset=787;
viewYOffset=1249;
tileRect=new Rectangle(0,0,mapTileWidth,mapTileHeight);
tilePoint=new Point(0,0);
trace(“viewCols=” + viewCols);
trace(“viewRows=” + viewRows);
trace(“setup map data”);
setupMapData();
setupWorld();
trace(“init and canvas”);
canvasBD=new BitmapData(viewWidth,viewHeight,false,0x000000);
//canvasBD=new BitmapData(viewWidth+2*mapTileWidth,viewHeight+2*mapTileHeight,false,0x000000);
trace(“init background”);
backgroundBD = new BitmapData(viewWidth,viewHeight,false,0x000000);
backgroundRect=new Rectangle(0,0,viewWidth,viewHeight);
backgroundPoint = new Point(0,0);
trace(“init buffer”);
//bufferBD=new BitmapData(viewWidth+2*mapTileWidth,viewHeight+2*mapTileHeight,false,0x000000);
bufferBD=new BitmapData(viewWidth+mapTileWidth,viewHeight+mapTileHeight,false,0x000000);
bufferRect=new Rectangle(0,0,viewWidth,viewHeight);
bufferPoint=new Point(0,0);
trace(“init 16×16 sprites”);
sprites16x16_width=160;
sprites16x16_height=160;
sprites16x16=new sprites16x16_png(sprites16x16_width,sprites16x16_height);
sprites16x16_perRow=sprites16x16_width/mapTileWidth;
//car
carPNG=new car_png(31,21)
carSprite=new Sprite();
carBitmap=new Bitmap(carPNG);
carBitmap.x=-15.5;
carBitmap.y=-10.5;
carSprite.addChild(carBitmap);
carSprite.x=110;
carSprite.y=145;
carRotation=-90;
carTurnSpeed=.5;
carMaxSpeed=5;
carAcceleration=.02;
carDeceleration=.03;
//canvasBitmap
canvasBitmap=new Bitmap(canvasBD);
addChild(canvasBitmap);
addChild(carSprite);
//message panel
mPanel=new messagePanel();
mPanel.x=50;
mPanel.y=50;
addChild(mPanel);
gameTimer=new Timer(_period,1); //changed in part 3 from 50
gameTimer.addEventListener(TimerEvent.TIMER, runGame);
gameTimer.start();
//key listeners
stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownListener);
stage.addEventListener(KeyboardEvent.KEY_UP,keyUpListener);
}
[/cc]
The variable definitions. This will give you an idea of how to set up all of the variables in the demo.
[cc lang=”javascript” width=”550″]
var mapTileWidth:int;
var mapTileHeight:int;
var playerObj:Object=new Object();
var canvasBD:BitmapData;
var backgroundBD:BitmapData;
var backgroundRect:Rectangle;
var backgroundPoint:Point;
//level xml
var XMLLevelData:XML;
//world
var aWorld:Array=new Array();
var worldCols:int;
var worldRows:int;
var worldTileWidth:int;
var worldTileHeight:int;
var worldWidth:int;
var worldHeight:int;
//the buffer is 2 tiles longer and higher than he view
//well first copy all of the tiles to the buffer
//then copy just the portion we need to the view
var bufferBD:BitmapData;
var bufferWidth:int;
var bufferHeight:int;
var bufferRect:Rectangle;
var bufferPoint:Point;
//view
var viewWidth:int;
var viewHeight:int;
var viewCols:int;
var viewRows:int;
var viewXOffset:Number;
var viewYOffset:Number;
//for drawing viewAreaTiles
var tileRect:Rectangle;
var tilePoint:Point;
//sprites16x16
var sprites16x16_width:int;
var sprites16x16_height:int;
var sprites16x16:BitmapData;
var sprites16x16_perRow:int;
//car stuff
var carPNG:BitmapData;
var carBitmap:Bitmap;
var carSprite:Sprite;
var carSpeed:Number=0;
var carAcceleration:Number=0;
var carDeceleration:Number=0;
var carMaxSpeed:Number=0;
var carRotation:Number=0;
var carDX:Number=0;
var carDY:Number=0;
var carTurnSpeed:Number=0;
//canvasBitmap
var canvasBitmap:Bitmap;
//message panel
var mPanel:MovieClip;
//Game Timer
public static const FRAME_RATE:int = 40;
private var _period:Number = 1000 / FRAME_RATE;
private var _beforeTime:int = 0;
private var _afterTime:int = 0;
private var _timeDiff:int = 0;
private var _sleepTime:int = 0;
private var _overSleepTime:int = 0;
private var _excess:int = 0;
private var gameTimer:Timer;
//keyboard input
var aKeyPress:Array=[];
[/cc]
With this code, you have 99% of what you need to re-create all three of the presented scrollers from scratch. The only thing left for you to do is create your own constructor for your class file. I have only left out one method, called setUpMapData(), because it simply is a giant embedded xml file that is placed in the XMLLevelData variable. You may not what to set up your map data this way, so you are free to fill the aWorld array with 2-d map data in any manner you choose.
That’s it. please email us @ info[at]8bitrocket[dot]com or leave a comment below if you are having any trouble, or want to provide improvements to the code or examples.