Log Entry #2
One week later and I haven’t been able to advance as much as I’d like.
It has mostly been a lot of planning. Here’s what has been decided so far:
- Cyberpunk theme. Story involving a mil-tech company that acquires a girls orphanage.
- Tilemaps will be loaded from the SD card to RAM
- The tileset (images of each tile) will stay in Flash
- Character customization… maybe.
- Code snippets in the TMX that gets called when the player steps on certain tiles.
I’ll try alternating my devlog posts between art and code. The previous post was all pictures, this one is going to be all code.
For this to work I’ll have to modify the TilemapConverter. The array of images will continue to be generated as it is now, but tile maps and metadata will now go into a single binary file.
Since I’ll be using RAM for tile maps, I’ll need to plan this carefully. FemtoIDE’s ELF file viewer is going to be useful for keeping an eye on how much space is being used, but there’s a hitch: When a variable is declared inline
, the symbol’s type in the ELF is V
… which could be either in flash or in RAM.
Before I can start, I need to know how much free RAM I actually have, so I need to update the ELF viewer to look at the address instead of the type.
Now that that’s done, time to reserve chunks of RAM.
- A 64x64 map will occupy about 8Kb of RAM.
- 8Kb for state data
- 7Kb for miscellaneous static data (palette, screenbuf, file handles, etc)
- 2Kb for stack
By rough estimate, there’s going to be over 10Kb of free RAM.
I’ll be using it with:
-
Sound streaming takes up a lot of RAM, so I’ll disable PokittoLib’s sound and I’ll port FemtoLib’s sound code from Java to C++. I’ll dedicate RAM2 to sound, but maybe not all of it will be needed. This jam is also going to be three months long, right?
-
Triggers on the TMX. That code has to go somewhere and it can’t be flash. I’ll load it in RAM1 when the player steps on that tile. A simple script could check if the player has a key and unlocks a door, for example. Or trigger some dialog. Or teleport to another point in the map. Or teleport to another map entirely. Or add an item to the player’s inventory.
-
A 32x32 sprite takes up 1024 bytes, so 8 sprites can fit into the remaining 8Kb.
Coincidentally, an RPG party is 4 people. Just enough RAM for 4 people vs 4 enemies.
In theory, since the sprites would be in RAM, it’s possible to do character customization.
Time to setup a custom TilemapConverter.
I want to be able to put triggers anywhere on a map, using Tiled. To do this, I’ll put the trigger in a script
property of an object.
The converter will read that property, wrap it in boilerplate and pass it to GCC.
Turns out GCC can accept code from STDIN (gcc -x c++ -
), so I don’t have to use a temporary file. Nifty.
By using hashes instead of strings for everything, I avoid any string comparisons and, more importantly, save space.
Once GCC compiles the code, it’s time to run the linker and get an actual executable.
The linker (LD) has no idea where the code is supposed to run. It also doesn’t know where RAM is, so it doesn’t know where to put variables. I need everything to fit in RAM1 and I’ll make LD put it there using a custom linker script.
The gist of it: It declares that all data and code go into the same region, located at 0x20000000 and at most 0x800 bytes long. It also tells the linker which function is the main entry point (otherwise LD would see that the function isn’t called and it would get removed).
The resulting elf file can then be converted into a bin using objcopy as per usual. The code from the previous screenshot results in an 86-byte bin. Looks like 2Kb is going to be more than enough for each “tile program”.
Now that all the maps are packed into an external file, loading one of them with File
is really straight-forward:
file.seek(mapOffset);
file >> mapWidth >> mapHeight;
file.read(tiles, mapWidth * mapHeight);
file.read(events, mapWidth * mapHeight);
As for the trigger, running it is also simple:
file.seek(pos);
file.read(RAM1, length);
auto trigger = reinterpret_cast<void (*)(const Parasite *)>(RAM1+1);
trigger(¶site);
And here’s the result of today’s work: