I’ve chosen to use XML for level markup. I know that XML has a bad reputation in a lot of circles, but this will not be a post about defending my choice.
For now, each level has it’s own XML document. To create a new level you need at least a <scene type=”level” name=”Bubbleman”> block and register it in the game descriptor file.
Of course, this will only create an empty level where the player is inserted at 0, 0 but from now on, we can start this scene with the Loader.startScene(sceneName); call.
Today I’m doing Bubbleman’s level.
The first thing we do when we describe a level is to define textures. We start with a big sprite sheet containing all graphics that belong to the level and specify which parts of the image can be used for what. I’ve built a very rudimentary tool for only that purpose, in which I can draw rectangles, name them and export this as XML.
In my world every tile is called an “animation”, even if it is only a single frame. I might refactor it into a better generic name in the future. The takeaway of this is that there is no value in differing by a multi-frame and single-frame tile at the definition stage. This is optimized in the parser and single-frame tiles will not animate.
With these preparations I start setting up reusable models which I paint using the tiles defined above.
The <geometry> tag tells the 3d engine what the mesh looks like. Like most of the time for a classic 2d platformer I define a plane. I set it’s size and specify how many segments it’s made up of. Then each face is painted using a the <tile> tag. I haven’t worked out all the namings so for this example consider tile and animation synonymous. The <tile> tag references an animation and says paint that animation on the faces in these ranges. I have a range parsing method that can extrapolate face indexes from expressions. <face x=”1–16/2" y=”2–15/2" /> means “paint x-segments 1 through 16 and y-segments 2–15 but skip every other”. What I’m doing here is setting up the waterfall background from Bubbleman’s level.
So how can I know it’s supposed to be defined like a raster? It requires some investigation. I have reference images to look at, but they of course does not animate. I also reverse engineer the original Megaman game by looking at it with my eyes, counting frames, comparing graphics and try to see what happens. It takes some time but it’s really fun to discover the techniques used all by myself. Then there is this technique which is pretty interesting; if I take the graphics from the original game, and scale it down so that every 16x16 pixels in downscaled to 1 pixel using nearest neighbour, a pattern emerge.
This sometimes exposes detail not apparent in the original. In this case a raster pattern pops out suggesting the background waterfall is a combination of two sets of waterfall animations. I can also use this map to know what face ranges I should specify since every pixel is exactly one face. I just measure it with Photoshop.
This will not work if you use a resampling filter lite bicubic when scaling down the image, since that will average everything out into a nice baby blue. Pretty but not useful.
With this information on hand I go thru the entire level and define models. The models are then referenced in the <layout> tag in the XML together with a position. This creates a clone of the original model and puts it in the world. I also specify a depth so that I can overlay models. Technically I could draw everything on one plane, but it’s much easier to work with it in layers.
The layout node also contains where different behaviors exists. Which part of the scene is solid, where you can climb and where you die instantly.
If I define objects with behaviors I can reference these ready made objects in the layout section instead but since these backgrounds are positionally static, there is no point in breaking out ladders and solids into separate objects.
Finally (at least for now) I specify where enemies pop up using spawner objects. I can just put the enemies there directly, but using spawners I can control the count, frequency how far they can roam and how long they are allowed to live. To express a single enemy that does not respawn I can just set the count to 1. The below spawner nodes are taken from Heatman’s stage.
So far so good.