8bitrocket.com
27Sep/081

Tutorial: N-way tile-based blit fine scrolling in AS3 (part 1)

Tutorial: N-way tile-based blit fine scrolling in AS3 (part 1)

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. In this example we will talk about simulating an overhead 2-d car driving game, but this technique can be used for any game where the player can rotate to an angle before walking / moving. In our example the car will stay in the middle of the screen and the background will scroll beneath it. This can easily be modified for 4 or 8 way scrolling game because while the movement in those types of games is not based on a rotated direction, the method by which we scroll the screen is identical. We will also examine an example of both 4 and 8-way scrolling.

This is a long tutorial, so it has been split into 3 parts:

Part 1 (this) explains the concepts of N-way blit scrolling

Part 2 adds a car and and let you drive it around the screen

Part 3 demonstrates 4-way and 8-way techniques and provide some auxiliary methods and functions that I have used in creating these demos.

This is an advanced tutorial. I need to cover a lot of ground very quickly, so if you need to know the basics of blitting or how to create a game timer loop, please consult these tutorials first:

The Basics of Tile Sheet Blitting

Creating an Optimized Game Timer Loop

What is Blit scrolling?

Blit scrolling is a method by which we take a large tile map and only show and update the portion that fits on the output window. This is NOT the same as storing the ENTIRE world as one large BitmapData object and then cutting out the portion we need for the window. While that is a viable method for creating a scrolling world, it limits the physical size of the world we can create. With tile based blit scrolling (sometimes called dynamic or just in time scrolling), our world size has virtually no limitations. We will actually store the entire world map as a 2d array (read from xml). Each element of our 100x100 array will simply be an integer that corresponds to a tile on our tile sheet. To be accurate, we actually only show what's in the output window, but will update a window buffer that is one tile size longer and taller than out output window. This "scroll buffer", as will refer to it, is used to fine scroll the game play window.

Why only theory and practice?

By this I mean that is is more useful to understand the concepts presented than to be simply force-fed a full set of application code to copy and paste. The process of scrolling a tile-based blit screen is very complicated, but not so complicated that a good developer can't follow the theory and create his own version. It is much more important for the advanced developer to understand the theory and see it demonstrated than to peer at my .fla and .as files. It will be showing most all of the necessary code, but it is more important that you understand the theory to apply to your own code if needed. If you simply want an .fla file to copy, then this tutorial is definitely not for you. The purpose of this tutorial is to teach you the skills you need to write your own scrolling engine. That being said, you are free to use of of the code provided in your own games, but please do not use the assets provided as the copyright remains with Atari.

The world v. the window, v. the buffer

The world refers to the entire map of tiles that the player can scroll over. The window is the current set of tiles that is viewable to the user. The buffer is one extra tile width and height around the entire world. We first draw to the buffer, then figure out how many pixels to scroll over in the buffer and then draw that portion to the output window. We are not talking code here yet, but when I say draw, and scroll, I am referring to the copyPixels method of the AS3 BitmapData class and the Rectangle and Point used for that operation.

Fine Scrolling

Fine scrolling gives us the ability to scroll the screen 1 pixel at a time in any direction. This is opposed to "tile scrolling" where the screen is scrolled up, down, left, or right by one entire tile at at time. Tile scrolling (or coarse scrolling) is a much easier method to accomplish as you need only know what tile to start with in the upper left-hand corner and then how many to show in the horizontal and vertical directions. When the screen is scrolled, we start painting tiles based on an x an y offset value. If the x offset is 1, then the screen is scrolled by 1 tile in the x direction, If the y offset is 2, then the screen is also scrolled in the y direction by 2 tiles. Since 0x,0y is the upper left hand region of the screen, the xoffset and yoffset can never be less than 0. When the user presses the up key, the yoffset is decremented by 1, when the users pressed the down key, the yoffset is incremented by 1. The same holds true for the x direction, with left decrementing and right incrementing by 1. The max we scroll to the right is the world width - the window width. The max we scroll down is the world height - the widow height.

This is an example of coarse tile scrolling. Use the arrow keys to move around the world. It scrolls on tile at a time. (make sure to click on the Flash window first).

As you can see, tile by tile scrolling is FAST, and it is suitable for many type of games (Bounderdash, Phantasie, etc). An overhead driving game is not one such game though. For that type of game, we will want to use Fine Scrolling.

With fine scrolling we use the exact same concept, but we scroll the screen 1 pixel at a time (as opposed to one tile at a time). The easiest fine scrolling method is the 4-way method. In this method, when the user presses up, down, left, or right, the xoffset and yoffset are still adjusted by 1 accordingly. The next thing we must do is figure out what tiles to draw to the window and the scroll buffer. Much like the 1 tile at a time movement approach, we can use very simple arithmetic to figure out what tile we need to start the draw from the world to the buffer. We will be doing something a little more complicated. We are going to scroll the screen based on the acceleration and turning of a car. This will allow ue to scroll the screen in N directions based on the angle of the car and some simple trigonometry. If you are interested in 4 or 8-way scrolling, all of the below concepts are applicable. At the end of this tutorial, I will show the modifications necessary to do 4 and 8-way scrolling over the same world.

How is it done?

First, we have this tile sheet in out library: has an export name of: sprites16x16_png

It has room for many 16x16 tiles, but we are only going to use a few for this example. These *might* look familiar to a few people because they were re-drawn by me to resemble the tiles in the obscure Atari/Kee Games classic, Super Bug.

We also have a set of xml data that defines out 100x100 tile world. It looks a little like this:

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

00000
...

[/cc]

The data for the world has 100 tilerow elements, each with 100 tilecol elements. They element values range from 0-5, corresponding to a tile in the tile sheet above.

This xml data is parsed and put into a 2d array called aWorld. (the parsing method example is at the end of this tutorial);

First, let's set up the world and give us a place to paint the screen.

Here is our starting info about the world. We will start with some variable definitions: The following code is in the init() function for my game.

[cc lang="javascript" width="550"]
mapTileHeight=16;
mapTileWidth=16; // the output window width and height in pixels
viewWidth=240;
viewHeight=320;
//our scrollable world in tile height and width
worldCols=100;
worldRows=100;
worldWidth=worldCols*mapTileWidth; //world width: 16*100=1600 pixels
worldHeight=worldRows*mapTileHeight; //world height: 16*100=1600 pixels
viewCols=viewWidth/mapTileWidth; // columns in view:240/16=15
viewRows=viewHeight/mapTileHeight; //rows in view: // 320/16 = 20

[/cc]

So, we have a world that is 1600x1600 and a window that is 240x320. We will need a canvas (our output window) to blit to that is 240x320, and a buffer that is 240+(tileWidth) and 320+(tileHeight). We only need to store a buffer of 1 tile width in each direction because that is all we will ever need to scroll over to fine scroll be 1 pixel in each direction. We should note that the speed of our player should never be > that the tileWidth or tileHeight.

In this case, the buffer will be 256 x 336. We also need one display object to place the canvas on the screen.

[cc lang="javascript" width="550"]
canvasBD=new BitmapData(viewWidth,viewHeight,false,0x000000);
bufferBD=new BitmapData(viewWidth+2*mapTileWidth,viewHeight+2*mapTileHeight,false,0x000000);
canvasBitmap=new Bitmap(canvasBD);
addChild(canvasBitmap);
[/cc]

I have decided to place the car at the start of my track, which is in the lower middle of the world. The starting xoffset and yoffset is:

[cc lang="javascript" width="550"]
viewXOffset=787;
viewYOffset=1249;
[/cc]

So, when we start running the application, we will need to first find what tile represents that starting position, then paint the buffer and finally paint the output screen.

The following code resides in a function that I call drawView() and it is called once per frame tick. You would place a call to this method in your main loop. I will give an example of a main loop later in this tutorial.

[cc lang="javascript" width="550"]
//calculate starting tile position

var tilex:int=int(viewXOffset/mapTileWidth);

var tiley:int=int(viewYOffset/mapTileHeight);

//tilex=int(787/16). tilex=49;

//tiley=int(1249/16). tiley=78;

//set up loop counters var rowCtr:int=0;

var colCtr:int=0;

//tile num is used to hold the index of the current tile on the tile sheet

var tileNum:int;
[/cc]

The next thing we do is loop through the tiles that make up the current screen + buffer and paint them to the bufferBD.

[cc lang="javascript" width="550"]
for (rowCtr=0;rowCtr<=viewRows;rowCtr++) {
for (colCtr=0;colCtr<=viewCols;colCtr++) {
tileNum=aWorld[rowCtr+tiley][colCtr+tilex];
tilePoint.x=colCtr*mapTileWidth;
tilePoint.y=rowCtr*mapTileHeight;
tileRect.x = int((tileNum % sprites16x16_perRow))*mapTileWidth;
tileRect.y = int((tileNum / sprites16x16_perRow))*mapTileHeight;
bufferBD.copyPixels(sprites16x16,tileRect,tilePoint);
}

}

[/cc]

We start by looping through the rows from 0 through 20 (or 21 rows including the 1 needed for the buffer). The inner loop marches through 0 to 15 (or 15 columns + 1 needed for the buffer).

We use standard tile math to pick the correct tile to paint from our tile sheet (sprites16x16) . We move the tilePoint to the appropriate location in the window to paint, and move the rect x and y to the correct starting location for the tile on the sprites16x16 tile sheet.

The bufferBD now is 21 rows by 16 columns, but we only need to paint 15x20. The next thing we need to do is find the exact portion of the buffer that needs to be painted to the canvasDB (our window)

[cc lang="javascript" width="550"]
bufferRect.x=viewXOffset % mapTileWidth;
bufferRect.y=viewYOffset % mapTileHeight;
canvasBD.copyPixels(bufferBD,bufferRect,bufferPoint);

[/cc]

Here, we first simply must calculate the starting x and y positions to copyPixels from the buffer. For this first frame in our example, it would be:

787 % 16 = 3x

1249 % 16 = 1y

So, the rectangle used to copy Pixels from the bufferBD to the canvasBD would start at 3,1 and would copy 240x320 pixels. It would place those a 0,0 on the canvasBD.

Go on to part 2

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.
Filed under: Tutorials Leave a comment
Comments (1) Trackbacks (0)

Leave a comment

No trackbacks yet.

This site is protected by Comment SPAM Wiper.