If you haven't used Tiled, I strongly recommend it. It's a platform agnostic tile based mapping tool with a number of nice features. A particularly nice one is CSV export - a format that can be very quickly converted into byte code or something else.
I won't cover how to make or organize a tile map here - that is something covered in detail in a number of places. Skipping ahead, let's assume you've made a simple map something like this:
In our case, we want it as an .h file we can include in our SGDK project. After exporting the map as a CSV, we get the following data, or something like it:
The entirety of the python script I wrote to convert the CSV to a .h file is here (explanation below):
# csv2c.py
import sys
if(len(sys.argv) != 2): # 1 arg only
print('error')
sys.exit()
f = open(sys.argv[1], 'r')
csvtxt = f.read()
f.close()
lines = csvtxt.split('\n')
i = 0
csvtxt = '' # split at line break bytes
while i < len(lines)-1:
csvtxt = csvtxt + lines[i] + ',\n'
i += 1
i = 0
chars = [] # make output array
while i < len(lines):
b = lines[i].split(',')
j = 0
while j < len(b):
if (b[j]) != '':
chars.append(b[j])
j += 1
i += 1
wid = int(len(chars)**0.5)
i = 0
outchars=[]
outchars.append(wid) # compress w RLE
while i < len(chars):
if (i < len(chars)-2):
if (chars[i] == chars[i+1]) and (chars[i] == chars[i+2]):
mc = chars[i]
outchars.append('254') #0xfe
count = 0
while (mc == chars[i]) and (i < len(chars)-2):
count += 1
i += 1
i = i - 1
outchars.append(str(mc)) #char
outchars.append(str(count)) #times
else:
outchars.append(chars[i])
else:
outchars.append(chars[i])
i += 1
outchars.append(len(outchars)-1) # end compression scheme
convt = '\t'
fn = sys.argv[1].split('/')
i = 0
while i < len(outchars): # tabulate str
convt = convt + str(outchars[i]) + ','
if ((i+1)%16==0):
convt = convt + '\n\t'
i += 1
outstr = "#ifndef HEADERSET\n#include <genesis.h>\n#endif\n//map[0] = mapWidth, map[..n] = size of array -2\nconst u8 " + fn[len(fn)-1][:-4] + "["+str(len(outchars))+"] = {\n" + convt + "\n};"
f = open(sys.argv[1][:-4]+'.h', 'w')
f.write(outstr)
f.close()
print(str(len(chars)) + ' written')
The string manipulation is fairly standard, so I'll explain the compression scheme:
wid = int(len(chars)**0.5)
i = 0
outchars=[]
outchars.append(wid) # compress w RLE
This first bit takes the square root of the number of tiles, 256 for a 16 by 16 map, and adds that number (i.e. sqrt(256) = 16) as the first digit in the array. This is optional, it's so my game can know how big the map is for other code.
while i < len(chars):
if (i < len(chars)-2):
if (chars[i] == chars[i+1]) and (chars[i] == chars[i+2]):
This iteration looks confusing. My RLE compression looks like this:
1. If the byte is NOT 0xFE, copy through2. If the byte IS 0xFE:- The next byte is the value to copy- The following byte is how many times to copy it- Move to next byte, back to 1.
That means the following are true:
"1 1 1 1 2" = 0xFE 0x01 0x04 0x02"2 2 2 1 1" = 0xFE 0x02 0x03 0x01 0x01
So when compressing our raw map, we need to look ahead a minimum of two bytes to see if a 0xFE scheme is needed.
mc = chars[i]
outchars.append('254') #0xfe
count = 0
while (mc == chars[i]) and (i < len(chars)-2):
count += 1
i += 1
i = i - 1
Next we search ahead, increasing our 'i' iterator as we do, and count the number of times we see the same number. The tricky part is the 'i = i - 1' at the end due to the post-increment. Python will still fall through to the final 'i = i + 1', and if we don't decrement i by one, we'll accidentally skip the next byte in the map.
outchars.append(str(mc)) #char
outchars.append(str(count)) #times
else:
outchars.append(chars[i])
else:
outchars.append(chars[i])
i += 1
outchars.append(len(outchars)-1) # end compression scheme
We need else's in both nests of the for loop to ensure all characters are written. It looks awkward but it is correct - though there might be a prettier way of doing it!
Finally, we append the number of COMPRESSED characters to the end of the array - this is so my game has the length of the decompression routine ready to go.
The script outputs a .h file that looks like this:
Note that the array is a const. SGDK/GCC should put this in ROM without prodding. Finally, the C:
u8 map[256];
u8 mapWidth;
void LoadDungeonMap(const u8* dungeon){
u16 mapctr = 0;
mapWidth = *(dungeon + 0);
u16 dlen = *(dungeon + sizeof(dungeon));
for(u16 i = 1; i < dlen; i++)
{
u8 nb = *(dungeon + i);
if(nb == 0xfe){
u8 ct = *(dungeon + i + 2);
nb = *(dungeon + i + 1);
for(u8 j = 0; j < ct; j++){
map[mapctr++] = nb;
}
i = i + 2;
}
else {
map[mapctr++] = nb;
}
}
}
To call it:
int main(u16 hard){
(...)
LoadDungeonMap(&maptest2);
}
}
Note that when calling, we use & to pass the address of the first entry of the const array. When defining the function, we use * to indicate that the variable we pass is a pointer, which contains an address.
Then to access the variable contained within the pointer's address, we use the * operator on the address of dungeon, offset by + i to get the byte value. Then we perform our RLE decompression, writing bytes to map[] (declared global to not be optimized away).
There you have it. No memcpy or extra allocation, just nice, straightforward RLE decompression for loading maps!