When talking about maps for games, XML comes up all the time. XML makes great sense with it's readibility and ease of parsing. Then I see people use XML to define their map but to save space, they condense everything heavily inside of it. This makes little sense to me. You lose the readability and the ease of parsing in favor of a smaller file size. The trick is that if file size matters and readability doesn't, lose XML entirely and go for something better suited to small file sizes.
With these requirements, I recomend using a binary file format directly and to heck with human readable at that point. Our next project uses this everywhere and it has turned huge maps into tiny files we just load via HTTP. Very clean, simple and incredibly fast. It also has the advantage of taking very little memory because converting from binary to AS3 data types has so little overhead (unlike string --> number parsing).
So let's say you have a 10x10 map. For simplicity, "X" is rock, "O" is grass, and "=" is path. An ASCI version might look like this:
X X X X X = X X X X
X O O O O = O O O X
X O O O O = O O O X
X O O O O = O O O X
X O O O O = O O O X
= = = = = = O O O X
X O O O O O O O O X
X O O O O O O O O X
X O O O O O O O O X
X X X X X X X X X X
This map lets you walk anywhere in the area but you can only leave via the path that enters on the left and exists at the top. Rocks surround everything with grass filling in the gaps. Boring, but a very real-world example.
Now you need to represent that with parsable data. The map data needs to contain two entries per tile, a tile type ID and a boolean for walkable/non walkable. In this case, rock is 1, grass is 2, and a path is 3. The 0/1 next to it indicates if the tile is walkable or not.
Here is an XML version I snagged from a thread on FlashKit game developers forum (sorry Mr. Malee).
<map name="map1" mapX="10" mapY="10">
<layer name="FLOOR">
<r>1:0 1:0 1:0 1:0 1:0 3:1 1:0 1:0 1:0 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0</r>
<r>3:1 3:1 3:1 3:1 3:1 3:1 2:1 2:1 2:1 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 2:1 2:1 2:1 2:1 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 2:1 2:1 2:1 2:1 1:0</r>
<r>1:0 2:1 2:1 2:1 2:1 2:1 2:1 2:1 2:1 1:0</r>
<r>1:0 1:0 1:0 1:0 1:0 1:0 1:0 1:0 1:0 1:0</r>
</layer>
</map>
Pretty big, even when using the condensed row format. This comes out to 579 bytes. Fall_X (another FlashKit poster) suggests to just use text if you are going to make your XML still require string.split style parsing. I agree, the format can then be:
1:0 1:0 1:0 1:0 1:0 3:1 1:0 1:0 1:0 1:0
1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0
1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0
1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0
1:0 2:1 2:1 2:1 2:1 3:1 2:1 2:1 2:1 1:0
3:1 3:1 3:1 3:1 3:1 3:1 2:1 2:1 2:1 1:0
1:0 2:1 2:1 2:1 2:1 2:1 2:1 2:1 2:1 1:0
1:0 2:1 2:1 2:1 2:1 2:1 2:1 2:1 2:1 1:0
1:0 2:1 2:1 2:1 2:1 2:1 2:1 2:1 2:1 1:0
1:0 1:0 1:0 1:0 1:0 1:0 1:0 1:0 1:0 1:0
But even that is still pretty large. Saved as a text file, it is 408 bytes. While this seems small, this is just a 10x10 grid with almost no data. When you make it a 20x20 grid (still quite small) the file size jumps to 1618 bytes.
How would you represent this with binary? You would first need to define the width and height (in tiles) of the map using the smallest logical data type. In this case, we would use a short. A short is a whole number represented by two bytes that goes to a max of 65k or so. The format would look something like this ([ ] indicates many - ie, iterating over them):
<Map Width (short)><Map Height (short)>[<Tile Type ID (short)><Is Walkable (boolean)>]
Since we know the exact length in bytes of shorts and booleans (1 byte) then we can easily determine how large this will be for any given map. The header is 4 bytes, each tile is 3 bytes. The 10x10 map above has 100 tiles defined. Final size? 304 bytes for a 10x10 map. What about our 20x20 example? 1204 bytes.
Now you might think that this is a trivial gain, and it is UNTIL you start to realize the more subltle differences. The example map has 3 tile types. Let's say your game has 20 tile types and you decide to replace 2 (grass) with 18 (snow covered grass), 1 (rock) with 19 (snow covered rock) and 3 (path) with 20 (snow covered path). What is the impact on your file size if you are using the text format? The 10x10 map becomes 508 bytes and the 20x20 becomes 2,018 bytes. A 40x40 map would be 8,038 bytes while an 80x80 would be 32,078 bytes.
How big is the binary map? It's exactly the same size as it was before. This is because the "short" we are writing is already capable of holding up to 65k+ tile types without changing. In fact, if you were going to have 256 or less tile types, you could even use a single byte to represent them. If you were going to have 128 tile types or less, you could use a single byte to represent both the tile type AND the walkable state of the tile. This is done by simply taking the most significant bit in the byte and toggling it on or off as needed then ignoring it when determining the number. Thus saving even more space!
So realistically, you probably will have 128 or less tile types in the average Flash game but you could have a map >256x256, so the header should still use "shorts" unless you want to go beyond 65k of tiles in each dimension. With this in mind, the new binary format becomes:
<Map Width (short)><Map Height (short)>[<Tile Type ID and walkable state(byte)>]
Now let's try those sizes again:
10x10 map - 100 tiles
Text (not XML) = 508 bytes
Binary (1 byte/tile) = 104 bytes
Binary (2 bytes/tile = 204 bytes
Binary (3 bytes/tile) = 304 bytes
20x20 - 400 tiles
Text (not XML) = 2,018 bytes
Binary (1 byte/tile) = 404 bytes
Binary (2 bytes/tile = 804 bytes
Binary (3 bytes/tile) = 1,204 bytes
40x40 - 1600 tiles
Text (not XML) = 8,038 bytes
Binary (1 byte/tile) = 1,604 bytes
Binary (2 bytes/tile = 3,204 bytes
Binary (3 bytes/tile) = 4,804 bytes
80x80 - 6400 tiles
Text (not XML) = 32,078 bytes
Binary (1 byte/tile) = 6,404 bytes
Binary (2 bytes/tile = 12,804 bytes
Binary (3 bytes/tile) = 19,204 bytes
As you can see, there is a massive size difference and it gets better and better as the map grows larger. In case someone is interested, the reason for this is simply that text in a file takes several bytes under the covers to represent it (depending on the encoding, etc). But when you read/write binary directly, you don't have this issue. You know exactly what will be created and how large it will be. Additionally, all the tricks used to make strings smaller work here too, run-length encoding, compression, etc. are all viable options. Flash even has built-in support for compressing/decompressing a byte array!
For more information on binary and how bytes break down into bits and how you can use em to store information: http://www.freesoft.org/CIE/Topics/19.htm
Now all this is well and good, but how do you use binary in Flash? Assuming you have AS3, it's a piece of cake. You simply need to load your data via HTTP using the appropriate loader class and use it to get at the ByteArray object. Then accessing bytes is trivial:
// Get the initial data array
var inputArray:ByteArray = ... // Data loaded externally into a byte array
// Read all the available parameters
var mapWidth:int = inputArray.readShort();
var mapHeight:int = inputArray.readShort();
// Iterate over the map
for(var y:int = 0; y < mapHeight; y++) {
for(var x:int = 0; x < mapWidth; x++) {
// Assuming the 3-byte per tile model
var tileTypeId:int = inputArray.readShort();
var isWalkable:boolean = inputArray.readBoolean();
}
}
Piece of cake. Now how do you save binary in the first place - you go in reverse. You build your ByteArray up and then have to fight with Flash a bit. Since Flash can't directly save data, you need to use something else. I'm betting you could do it with AIR, but I know you can do it with PHP. Easiest solution is to use Base64Encoder in Flash and encode the byte array to a string, then send that string to PHP and have it decode using the built-in decode (link) then simply have PHP save the binary data to the file system.
Now, if you don't want to do the "true" binary file format but you want some of the other advantages binary gives you, you can still just use the Base64 encoded string. It is text and totally usable as such. Just copy it out of a text field and throw it into a text file. Instant binary-ish file format
Nice, clean, and powerful. With binary being finally supported properly, there is very little reason not to take advantage of it. Anyways, enough from me. Have fun!