Quantcast
The World Famous 8bitrocket.com Home Page



Flash CS3: Actionscript 3 (AS3) Game Primer #1: Tile Maps, XML, and bitmapData
8/25/2007 12:00:00 AM by: Jeff Fulton
Category: Tutorials, Series:AS3 Bitmaps , Syndication:(XML/RSS)

Blogs In This Series


Blogs In This Category


Last 10 Blogs

ne of the most useful advances in Flash 8 and CS3 for game developers has been the use of raw bitmap data to render on screen images. The CS3 / AS3 implementation has been changed from AS2, so this tutorial will go through the basics of creating, loading, and displaying a screen of bitmap data. We will first create a unique set of image tiles and export those tiles as a PNG file. Next we will load those tiles into a map creation tool (Mappy) and use them to create a screen for a Pacman style game. We will use a custom Mappy (.lua) script I wrote, especially for this tutorial, to export the screen data as XML. When we finally get to Flash, well write a class that loads the PNG tiles, loads the XML map data, and displays a bitmapData representation of the screen we create.

There are quite a few steps involved, so I will do a moderate amount of explaining, while also providing files to download and use.

Step 1: Creating a tile sheet.
A tile sheet is basically the same as any other image, but it is set up in a unique manner. Our tile sheet will be 512 pixels in width, and 64 in height. This will allow us to have 2 rows of 16 32x32 tiles to use. I won't go into exact details on creating tiles because the scope of this tutorial is based around programming and not graphic design. In any case, here are the basic steps I took to create my tile sheet.

1. I opened up Fireworks and created a new image 512x64.It can be up to 512 pixels in height, but I already have an idea of how many tiles I'll next and 64 is going to be enough. Why max 512x512? This gives us 16x16 or 256 images possible in out tile sheet. Also, a 16x16 grid is pretty easy to deal with for me (math-wise)and for computers.

2. I created a new image the size 32x32.

3. I set the the canvas background color to transparent and zoomed in on the image.

4.Using the pencil tool I edited each of the individual pixels.

5. When I felt I was complete with an image tile, I copied it to the 512x64 blank tile sheet. image. I was careful to put the first image at 0,0, and the next at 32,0, etc.

6. When I was done with my map, it looked like this:

I saved this as tiles.png.

Along with the background maze tiles, I also created tiles to represent the sprites for the game. I have two frames for a Pacman like character, frames for dots to eat, ghosts, and lastly a two frame animation for power pellets. One of the reasons I am using a .png file is to preserve the transparency of the sprites and also have my choice of millions of colors (not that I used more than 20 in this example). This one tile / sprite sheet will be enough for the entire game. Since we won't be using the ghost or Pacman characters yet, we'll refer to this as our tile sheet rather than out sprite sheet. BitmapData in Flash deals with both of  these (sprites and tiles) same though, so really one file is capable of handling both tiles an sprites.  It's all just bits, and how you intend to use them.

Step 2: Creating a maze out of the tile map.
One of the most useful weapons in the arsenal of any game developer is a good map editor. Now, as a Flash programmer, you can pretty much make one of these for your self, but I like to rely on a great map editor called Mappy. The basic version of Mappy is free, but I highly suggest spending the $19.00 it costs to register the full version.

Details on Mappy can be found at The Mappy Web Site. I also am not going to go into full details on using Mappy. I will go over the very basic steps that anyone can use to create a level map with Mappy. We are going top use the tiles.png tile sheet we created. The latest version of Mappy needs an extra .dll to use png files properly. So you should take careful note that there is a .zip file of DLLs that will need to be downloaded and copied to the Mappy folder.

Once you have downloaded, unzipped, and placed the Mappy files in a folder (there is no official install program), you will need to unzip the "libpng12.zip" (which can be obtained here) to use .PNG files with the program.

Now let's create out maze tile map.

1. Open Mappy.

2. In the [File] menu, select New Map and set the tile size to 32x32 and the map size to 15x15. The Pacman style level we will create can be easily created in 480x480 or 15x15 32 pixel width and height tiles.

3. Mappy will warn you that you will now need to import a tile sheet into the program to use. In the[File] menu choose Import and then select the tiles.png file we created in step 1.

4. The tiles will show up in the right-hand window. Take close look at the tile sheet and you will notice that a blank tile has been inserted at the beginning of the tile sheet. This is useful for deleting tiles from your map, but isn't an actual tile in your sheet. This will be important when we need to export our data for use in Flash.

5. I used the tiles to create a map that loops like the:

(Mappy showing the tile sheet on the right and the map I created on the left).

6. The next step is to save the file as "maze1.FMP". This will save a file with both the created maze data and the tile sheet together so you can go back in later and edit or update it.

7. I am not a Mappy expert, so there are probably other ways to accomplish the next task, but here is the method I used. We need to export the maze1.FMP data to a file that is usable in Actionscript. Since this tutorial is supposed to cover XML to some extent, that is the format I chose to use. Also, XML is a nice clean, standards based format that is easy to use with Actionscript 3. The problem is that Mappy cannot export directly to XML. At least not at first glance.

You might have noticed a [custom] menu in the Mappy menu bar. This menu contains scripts that have been provided that will allow you to perform custom tasks with Mappy. One of the current provided scripts is called: "Export Flash Actionscript". This is a very useful script that will export out the code to create a multidimensional array in Actionscript from ourt maze1 tile map. This is a great script in of itself, and if we desired to use an array to hold our tile data, it would be sufficient as it stands. With some slight modifications, though, it can be changed to export out an xml file representation of that map.

The scripts are located in the "/luascr" that is in the root of the Mappy program folder. I copied the "Export Flash actionscript.lua" file, renamed it "Export XMl.lua" and created this file: Mappy export as xml script for you to download.

One you have downloaded that script, you need to unzip it into the "/luascr" folder.

Next, you will need to simply edit the mapwin.ini file in the root of the Mappy program folder. You need to add a line in the custom scripts section that resembles this:

"lua16=Export xml.lua", under the "lua15=Set Text Strings.lua" (if that is the last one on the version you have). " lua16 " is simply is the next number in order sequence after the other scripts in the file.

Now we are ready to export the map data as xml.

  • Restart Mappy, load in the "maze1.FMP" file, and then choose the "Export XML" option from the [custom] menu.
  • The first thing you will see is a warning that what you are going to be exporting is simply a flattened version of the map, no animation will be exported.
  • Click [OK] and then type in a name for the xml file. For this example, use "maze1". It will add the .xml for you. If you add the ".xml" in yourself, you will have a strange looking file called "maze1.xml.xml". This mostly has to do with my being a complete novice at "luascript".
  • Click on [save]
  • Next it will ask you how much to adjust the map values. This is an important question because Mappy added that extra blank tile to the beginning of our tile sheet. Because of that, the actual map values are 1 greater than they will be when we use the original tiles.png file as our source for times in Flash. .
  • For that reason, put a "-1" into this dialog box and click on the [OK] button.
  • You will now have an exported xml file of the tile map.

The XML looks like this for the first row

<map>
   <tilerow>
      <tilecol>8</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>6</tilecol>
      
<tilecol>14</tilecol>
      <tilecol>7</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>2</tilecol>
      <tilecol>9</tilecol>
   </tilerow>
...</map>

Step 3. Using the tile sheet and tile map in Flash CS3 with AS3 
The next step is to take the maze.xml and the tiles.png file and put them in the same folder. In this same folder (for ease of this simple tutorial) create a new Flash CS3 file called. tileMapExample.fla

Change the document class to TileMapExample (in the document properties window).
Create a new AS3 script file called TileMapExample.as.

Here is the first section of code for the TileMapExample class:
package  {
   import flash.display.MovieClip;
   import flash.display.Bitmap;
   import flash.geom.*;
   import flash.display.BitmapData;
  import flash.display.Loader;
   import flash.net.URLRequest;
   import flash.events.*;
   import flash.net.*;

   public class TileMapExample extends MovieClip {

First we create a generic package to house our class. If this was an actual game called Maze Man, I might create a package com.8bitrocket.mazeman and then add all of the mazeman classes to that package. I would still keep the .fla in the root and maybe place the .png and .xml files in folders called /gfx and /maps. In any case for this relatively simple example, all of the files will be in the same folder.

Next we import in the packages and classes we will need to accomplish loading assets, using xml, and displaying bitmaps from tile sheets. Play special attention to the loader classes because loading assets has been completely changed in AS3.

The final line is our class declaration. This class will extend MovieClip because it is actually the root document class of the .fla file. Also, this makes it easy to use it as a base container for our example maze screen.

   private var loader:Loader; // The bitmap loader
   private var xmlLoader:URLLoader;
   private var mazeData:XML;
   private var aTileMap:Array=new Array();
   private var screenBitmapData:BitmapData;
   private var tilesBitmapData:BitmapData;
   private var screenBitmap:Bitmap;
   private var mapRows:int=15;
   private var mapCols:int=15;
   private var tileSize:int=32;
   private var screenWidth:int=mapRows*tileSize;
   private var screenHeight:int=mapCols*tileSize;
		
   public function TileMapExample() {
	//load in tile sheet image
	loader = new Loader( );
	loader.contentLoaderInfo.addEventListener(Event.INIT,tilesLoadInit);
	loader.load(new URLRequest("tiles.png"));
}
Here, we begin to instantiate all of the variables we will need for this example.
The loader variable will hold the raw loaded data from our tiles.png file.
The xmlLoader variable will hold the raw loaded in xml data from maze1.xml file.
The mazeData variable will hold the xml representation of the loaded in xml file.
The aTileMap variable will contain an 2 dimensional array of the maze tiles to display. These are rganized by rows, then columns in each row.
The tilesBitmapData variable will hold a bitmapData representation of the tiles.png loader (Loader var)
The screenBitmapData variable will hold the bitmapData representation of our screen to display (aTilesMap array).
The mapRows variable holds the number of row tiles for the screen.
The mapCols variable holds the number of column tiles for the screen.
The tileSize variable holds the map square tile size. In this case our tiles are 32x32, so I just use one variable for both. This should be changed to tileWidth and tileHeight if you have non square tiles.
The screenWidth and screenHeight are calculated using the tileSize and mapRows, mapCols variables respectively. They are used to instantiate the screenBitmapData.

The next 5 lines create the TileMapExample constructor, and load the "tiles.png" into the loader variable.
When the loader has completed, it calls the "tilesLoadedInit" function referenced in this line: loader.contentLoaderInfo.addEventListener(Event.INIT,tilesLoadInit);

URLRequest is a new object, and its purpose is to capture all of the information in a single HTTP request.

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("maze1.xml"));
}

When the tiles.png is finished loading, it will be contained in the loader.content variable. To create a bitmapData object out of it, you must cast it as a Bitmap date type and then access the bitmapData property of the casted loader content. It sounds complicated, and it actually is. The loader object contains a data representation of the png file, but it is not a Bitmap type or a BitmapData type. But, it can be casted as a Bitmap type and then the bitmapData property will be accessible (whew!).

The next line simply calls the loadMapXML function;

The loadMapXML function creates a URLLoader object to load the XML into. The xmlLoadComplete function will be called when the loading of the xml file has finished. The URLLoader is a replacement for the old loadVars function. It takes a parameter of URLRequest, which in this instance is simply an new instance of URLRequest with the "maze1.xml" file passed in to its constructor.

The rest of the class follows, it is the real meat of the tile screen display.

private function xmlLoadComplete(e:Event):void {
   mazeData=new XML(xmlLoader.data);
   //loop through xml and add rows and columns to aTileMap array
   for (var rowCtr=0;rowCtr<mapRows;rowCtr++) {
        var tempArray:Array=new Array();
	trace("row=" + rowCtr);
	for (var colCtr=0;colCtr<mapCols;colCtr++) {
	   trace("col=" + colCtr);
	   trace(mazeData.tilerow[rowCtr].tilecol[colCtr]);
	   tempArray.push(mazeData.tilerow[rowCtr].tilecol[colCtr]);
	}
	aTileMap.push(tempArray);
    }
			
			
   //using the data in the array add to screenBitmapData
   screenBitmapData=new BitmapData(screenWidth,screenHeight,false,0x000000);
			
   //screenBitmapData.copyPixels(tilesBitmapData,new Rectangle(0,0,32,32), new Point(0,0));
   for (rowCtr=0;rowCtr<mapRows;rowCtr++) {
				
	for (colCtr=0;colCtr<mapCols;colCtr++) {
	   var tileNum:int=int(aTileMap[rowCtr][colCtr]);
	   var destY:int=rowCtr*tileSize;
	   var destX:int=colCtr*tileSize;
	   var sourceX:int=(tileNum % 16)*tileSize;
	   var sourceY:int=(int(tileNum/16))*tileSize;
           
var sourceRect:Rectangle=new Rectangle(sourceX,sourceY,tileSize,tileSize);
          var destPoint:Point=new Point(destX,destY);    screenBitmapData.copyPixels(tilesBitmapData,sourceRect,destPoint); }    }    //display screen by adding screenBitmapData to screenBitmap and add screenBitmap data to displaylist    screenBitmap=new Bitmap(screenBitmapData);    addChild(screenBitmap);    }   } }

Once the maze1.xml file is completely loaded, the xmlLoadComplete function is called.
The first task we accomplish is to instantiate the mazeData XML instance by passing in the xmlLoader.data variable.  The will accept a string formatted at XML.
The nested for loop simply loops through xml <tilerow></tilerow> tags  inside our xml data. It then loops through all of the <tilecol></tilecol> tags in each <tilerow> pushes the number contained in each a tempArray. Once each row has completed, that row is pushed to the aTileMap array. This is a pretty simple, yet powerful method to easily get all of the xml data into a usable multidimensional array.

screenBitmapData=new BitmapData(screenWidth,screenHeight,false,0x000000) - Here we instantiate the screeenBitmapData instance of bitmapData to be 480x480, with no transparency, and a black background.

The next set of nested for loops contain a lot of information, so we will examine it in detail.

for (rowCtr=0;rowCtr<mapRows;rowCtr++) {
This line is pretty self explanatory. It simply creates a for loop to run through all of the map rows to be displayed.

for (colCtr=0;colCtr<mapCols;colCtr++) {
This line loops through all of the columns in each row.

var tileNum:int=int(aTileMap[rowCtr][colCtr]);
Here, we are setting the tileNum by referencing the current row and column of the aTileMap array. This number represents the tile number from our tile sheet. We'll  use it  to copy the pixels from that location to our screen.

var destY:int=rowCtr*tileSize;
var destX:int=colCtr*tileSize;

The destX and destY hold the upper left hand corner of the 32x32 square to copy the tile sheet tile to our screen. Since we know the tileSize, we only need to multiply the current rowCtr and colCtr by the that tileSize to find the screen location for our tile.

var sourceX:int=(tileNum % 16)*tileSize;
var sourceY:int=(int(tileNum/16))*tileSize;

The sourceX and sourceY variables hold the upper left hand corner of the tile to copy TO the screen from the tiles.png tile sheet. Because we have chosen to have a max 16x16 grid of tiles as our source map, we can easily find the location of our needed tile by applying the modulo operator to 16 and multiplying it by our tileSize to get the x value. We simply take an int (or a floor) of the tileNum/16 * tileSize to get the y value.

 var sourceRect:Rectangle=new Rectangle(sourceX,sourceY,tileSize,tileSize);
 var destPoint:Point=new Point(destX,destY); 
 screenBitmapData.copyPixels(tilesBitmapData,sourceRect,destPoint);
Our screenBitmapData is built by copying the source rectangle (sourceX and sourceX, 32,32) from the tiles.png (tilesBitmapData) and placing those pixels at the destX and destX of the screenBitmapData.

Finally, we cannot just display a bitmapData instance on the screen directly (we also cannot attach it to a movieClip as we did in AS2), we need to add it a Bitmap object and the add that Bitmap object to the main display list:

screenBitmap=new Bitmap(screenBitmapData);
addChild(screenBitmap);

Working example and source files

Next time we'll continue to refine this into a real Pacman clone game.



GamerBlips: vote it up!

Comments
optics: 2/27/2008 1:53:58 AM
m really trying to understand this part. Im working on a project that I would like to eventually use supertiles with due to the enormity of dynamic movie clips. But eventually ill be using very large png files. Is there any way you can help me adapt this code that so no matter the size of my source or dimensions it cuts them accurately? var sourceX:int=(tileNum % 16)*tileSize var sourceY:int=(int(tileNum/16))*tileSize The sourceX and sourceY variables hold the upper left hand corner of
optics: 2/27/2008 1:57:05 AM
m really trying to understand this part. Im working on a project that I would like to eventually use supertiles with due to the enormity of dynamic movie clips. But eventually ill be using very large png files. Is there any way you can help me adapt this code that so no matter the size of my source or dimensions it cuts them accurately? var sourceX:int=(tileNum % 16)*tileSize var sourceY:int=(int(tileNum/16))*tileSize The sourceX and sourceY variables hold the upper left hand corner of
Jeff : 2/27/2008 11:23:33 PM
Sure, the 16 is the number of tiles in a row. So, if you have a bitmap that is 200x400 with 50x50 tiles, you will have 2 tiles across and 4 down. In that case, you would use 2 instead of 16.
Jeff: 2/27/2008 11:25:18 PM
Sorry, I should have said 4 tiles across and 8 down, so 4 is the number you would use. In any case, tilesheet width/tile width (number of tiles per row) is the number to use in the calculation
Brent: 6/30/2008 11:29:00 AM
have you put out a primer #2 follow-up to this tutorial to put interactivity into the game?
Jeff Fulton: 6/30/2008 5:04:04 PM
Brent, that is in the works. It is part of my next series of tutorials.
Jay: 9/4/2008 3:15:29 AM
Just found up this Tutorial, nice one Jeff.. do you ever think want to create tutorial just like this one, but using an Array for the Image Data Jeff ? :d
btw thank you very much for writing a lot of Great tutorials.. it works fine for me..
Jeff Fulton: 9/4/2008 11:01:38 AM
Sure, an array would work fine. Do you mean for the level data (what tiles to put in what place) instead of XML? No matter what, Arrays will work just as well. In fact, I read all the XML into an array in this one. All of the BitmapData objects can also be stored in arrays,
randygland: 1/26/2009 2:37:21 PM
great tutorial.. are the next set of tutorials for this on this site somewhere?
Jeff: 1/26/2009 4:38:13 PM
Hey Randy. I branched off and did tutorials on game timers, optimization, scrolling, etc that were a spin off of this, but never made the final maze game (in tutorial form). The final game engine was used for 1/2 dozen games though, and is for sale ($30) at Flashden - called MicroChip Madness. It includes all source code, etc.
http://www.flashden.net/item/micro-chip-maze-engine/26905
I'm not saying to go buy the source sucker! I will eventually do tutorials on it, but if you are interested in seeing it right away with some decent docs, it is all there.
Tolis: 3/20/2009 7:40:13 AM
Great tutorial there!!

I have a question though...If i want to zoom in and out on the tile map should i rebuild every time the map using bigger or smaller sprites? Say i want to make a game with controls like google maps.
Jeff: 3/20/2009 12:31:27 PM
Hey Tolis,

One way to do that is to simply change the scaleX and scaleY properties of the Bitmap or the Sprite/MovieClip that is holding the BitmapData. It is probably a little more complicated to get it to fix the zoom on a particular part of the map, but the concept should be pretty simple to play with and find out. Let me know if that works. It is an interesting problem.
Kam: 3/28/2009 4:53:54 PM
Hey Jeff,

Great tutorial, its helped me with becoming one step closer to understanding OOP. I was wondering how adding multiple layers would happen, i was thinking it would just be the same for loops again but that could create a long loading time. Is there a simpler way?
Kam: 3/28/2009 4:56:59 PM
By the way i mean by layers as in mappy it has different layers, so we can add decals into the game, and have different depth of the movieclips.
Jeff: 3/29/2009 11:59:37 AM
Hey Kam,

I have not specifically used multiple layers in mappy, but essentially it would be the same basic idea. One thing to note is that usually one or more of the layers might be static and not need to be looped and re-painted each frame, rather it could be built into a full bitmapdata image and just ussed as a background layers (or fore ground layer) inside a different bitmap object in the main  displayList.

If there is a secomd layer of active tiles, then yes, I would loop through them also, but no need for multiple row and col loops. Just loop through the rows, and then the col in each row and then paint each tile in order at each location.  So, if you have 3 active layers, then you paint the bottom tile, then middle tile, then top tile at each location and then move tot he next. That way you don't waste time re-looping.
Pieter: 11/18/2009 3:40:00 AM

Hey Jeff,

Loved this tutorial, but isn't Pac-Man a rather improper example to start from? 
How would you, for instance, deal with the coins/dots being eaten? Would you change out every dot with a blank space in your code, when you create the map and then put them the dots on top of the map, each as a separate MC?

The Wush: 12/18/2009 1:04:16 AM
Nice tutorial, but if one were to use an array instead of XML how would one set up said array?
Add Your Own Comments
Message
Name:
Email:


This will not be displayed. It is used for a email one-time validation only.
Note: You MUST Respond To The Verification Email The First Time To Have These And all future Comments Displayed!
Validation Text:
Please type in validation text from the graphic.
Comment:

Max: 2500 Characters characters left


Questions? Comments?
For more information, contact: info@8bitrocket.com