8bitrocket Diatribe: Tile-based BitmapData Fine Scrolling

8bitrocket Diatribe: Tile-based BitmapData Fine Scrolling

I’m currently working on a 4 way scrolling car driving game, somewhat like Atari 800/C64 Rally Speedway or Mattel Intellivision’s Auto Racing. I have “borrowed” (for testing purposes only” the graphics from a very early Atari/Kee Games coin-op called Super Bug, which in itself was the first game of this kind (4-way scrolling, top-down driving game). It turns out that I have never made a 4-way scrolling game before. I have made horizontal scrolling racing games (here and here) (ages ago) with a GAS tile method (goto and stop) , but never a fully blit scrolling engine.

So, I spent the last few days researching the subject in various books, online articles and personal references (thanks, Cutler). I found that the the most common method for creating a tile-based BitmapData world is to create a HUGE world BitmapData object composed of your entire tile map (or as much would fit in the max BitmapData size). This world would NOT be rendered to the screen, but you would render a “camera” or “view” of that world by moving the copyPixels Rect location to the proper x,y coords and copying a screen-sized portion of the world to the view. My buddy Chris had made a game like this, and it sounded like an interesting, if restricting way to create a scrolling world. Chris had also created a very new game using a more complex method – render a screen sized portion of tiles (plus a bordering buffer tile all around) and use BitmapData.scroll() to move the output window to the proper place in the buffer and copyPixels.

Starting with Chris’s second idea, but absolutely no reference code, I spend the last week fumbling my way through my own lame interpretation that never quite worked very well. The best I came up with on my own worked great in the Right and Down directions, but going up and left the buffer would not fill in the top or left hand column until an entire tile was moved, creating a gutter effect. That version wasn’t even using the BitmapData.scroll() method, it was just creating a buffer with the extra tile around the entire world, and moving an offset when an arrow key was pressed. The offset would tell the final copyPixels operation where to begin the copy to the final output screen. I don’t know why I am going into detail because it created a gutter effect anyway, but my point is that I was never able to get the scroll() version to work. I tried, but scrolling the BitmpaData object created some awesomely strange visual effects that I could probably sell to Jeff Minter, but couldn’t use in my game.

Anyway, armed with a slightly working version, and the knowledge that Chris imparted on me (even though I was too obtuse to figure out exactly what he had done), I embarked on my new version tonight and I think I might finally have something. It isn’t optimized, and I am sure (hopefully) someone will email me with a list of things I can do better to make it more efficient, but I think I finally have one that works.

I have to thank Jonathan S. Harbour for his help on this. I have two of his books, one on Dark Basic, and one on 2d Windows game programming. I found an invaluable chapter in his Dark Basic book on doing exactly what I wanted. When I couldn’t get my jumble of spaghetti to scroll properly, I read and re-read his chapter about 5 times and then took his basic points and ported them to AS3. It doesn’t implement the BitmapData.scroll(), but uses an over scan like buffer to copy from. This creates TWO copy pixel operations: one to get each tile to the buffer and another to copy the correct portion of the buffer to the output. I have read that the scroll() method is basically doing another copyPixels, so I might not save anything even if I do figure out that method.

The (not complete in the least) pseudo-code (plus some real code) is here: (the real code is a jumbled mess with a HUGE set of embedded XML that looks like crap.
1. Create a canvas BitmapData object the size of the output window
2. Create a buffer BitmapData object the size of the output widow + 2 * the tileWidth for the width.
and + 2 * the tileHeight for the height
3. When a key is pressed, increment or decrement the xoffset or yoffest by 1 (depending on the direction pressed)
4.(here is the actual rendering code)

[cc lang=”javascript” width=”550″]
//calculate starting tile position
var tilex:int=int(viewXOffset/mapTileWidth);
var tiley:int=int(viewYOffset/mapTileHeight);
var columns:int=int(viewWidth/mapTileWidth);
var rows:int=int(viewHeight/mapTileHeight);
var rowCtr:int=0;
var colCtr:int=0;
var tileNum:int;

for (rowCtr=0;rowCtr<=rows;rowCtr++) {
for (colCtr=0;colCtr<=columns;colCtr++) {
tileRect.x = int((tileNum % sprites16x16_perRow))*mapTileWidth;
tileRect.y = int((tileNum / sprites16x16_perRow))*mapTileHeight;

bufferRect.x=viewXOffset % mapTileWidth;
bufferRect.y=viewYOffset % mapTileHeight;

5. The basics are that you first find the tiles you need to display and copy them to the buffer,
then you figure out what part of the buffer needs to be painted to the output canvas.

If this was a full tutorial, I would spend more time going over the code line by line. I am still working out the kinks, but here is the current version.
It starts in the upper left-hand portion of the map, and there are only 4 different tile types (straight from Super Bug). It scrolls slowly because the increment is 1 pixel at a time.

No optimizations have been implemented. This is pretty much straight out of my testing ground. Nothing revolutionary, but I’m happy to finally get something to work properly.

Here is the working version (arrow keys scroll 1 pixel at at time):

(note: as of 9/22/2008, scrolling in the max y direction causes an unhandled exception…I guess I need to handle my exceptions better! The x direction works fine, so I must have fudged something with my fat fingers when doing the y)

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.