Like the majority of games, Anodyne uses a map editor in order to create the levels and place enemies. This short post talks a bit about the technical details in creating the levels.
First we have the ideas that need to be implemented – an enemy idea, or a dungeon idea, etc. - so after I design some dungeon rooms or program some enemies, it’s time to put them into the map editor, which is a way to make your levels and export them into data that the game can use to create the actual areas. The map editor I use is DAME, which works well with Flixel, the AS3 framework I use for Anodyne.
The game’s maps are tiled in the editor and export to plain text in the CSV format – just lines of comma-separated numbers which correspond to specific tiles.
The CSV is then read by the game, and in conjunction with a tileset .png image file, creates the in-game environment, and additionally sets properties for tiles – such as callbacks (for holes), or whether or not you can walk on the tile.
The entities (treasure boxes, enemies, etc.) are also placed inside of DAME, but instead export to XML. Each map in the game exports a bunch of sprites for that map, as well as some metadata that I use in-game to give certain behaviors, and also the x and y coordinates, so my game logic knows when to instantiate a sprite, and how it should behave.
Sprites are loaded slightly differently depending on where in Anodyne the player is. In the game, there are “field” areas and “dungeon” areas.
In field areas, the camera moves with the player, much like the cameras in platformers. These areas are more for transitory, open-world like places. Currently, I just spawn all the sprites at once, which obviously isn’t very efficient. I don’t want to waste time optimizing what works fine, so if I reach the point where performance takes a hit I’ll probably stick in a distance metric that determines whether I bother updating a sprite, that has some timeout to see if the sprite should be revived. There's not much interesting here, in my opinion.
Anyways…on to the dungeon areas.
The dungeon areas are a little more interesting. Although the entire map CSV is loaded into memory, because the camera only sees 10x10 chunks of map at once, we only need to continuously draw a maximum of two 10x10 chunks. This helps to increase performance.
When you first enter a dungeon area, the initial chunk is loaded, and that’s where the player is instantiated. Then, I set a few bounds where if the player crosses them, we freeze the controls, load the next chunk into memory, and pan the camera to the next room. This way, I only need to maintain 3 tilemap objects, one, which contains the entire map in memory stored as an array, which makes it easier to obtain chunks for the 10×10 rooms. And then two 10×10 chunks because we want the previous room to still show when scrolling – so when we transition rooms, the current map buffer sends its data to a previous map buffer, and the current map buffer loads the next map.
The sprites also have to be arranged a little differently in order to load them on a per-room basis, so once at the start of the game, we take the very wide XML tree, and determine what sprites fall into which rooms, and create subtrees for each room. This way when we enter a room, we just lookup that subtree and instantiate all of its entities.
During a transition, I also move all the old sprites from the room being moved out to another array so that they still appear, and I then clear them out of memory once the map completely moves over. This way we don’t have an awkward disappearance of all the enemies in one room as we transition, and we additionally see all the entities in the next room as we transition.
The entire game’s XML is serialized, as the XML stores the state of enemies that are needed to be permanently dead (bosses), or entities that need to stay open forever once opened (gates, locked doors).
This means that updating a released version isn’t really an option, as we’d need a way to patch the game’s XML tree. Oh well. But it’s not like I want to release a game that isn’t content complete, so it’s not really an issue, I’d hope. Will just have to be careful with save bugs, which we will hopefully iron out in testing and so forth.
That’s the general idea for all of the dataflow, and there’s not much magic going on. Maybe later I’ll talk about how some of the basic entities work and communicate, which also isn’t particularly complex since only a limited set of entities actually interact with eachother, by design, for simplicity. Or, how I go about bosses, or the player interactions…hm.
If you’d like to know anything very specific about this process let me know and I’ll write something up about it!