top of page
ManavMaker:
2D Level Editor

2D level editor for platformers. The editor features an auto-tiling tilemap, mechanics from the game Super Mario Maker and,  customizable sprite sheets and soundtracks

 

Role

Programmer

Technology

Personal Engine (C++)

Team Size

1

Development Time

~6 months (Ongoing)

Goals

For my masters thesis, I wanted To make a level editor that can be used to
make Super Mario Maker - inspired levels with the option of using their own graphics and sounds.

  • Users should be able to add their own sprites, animations and, sounds
    into the game via XML

  • Users should be able to edit a tile-based level with comfortable and intuitive controls

  • Users should have access to a variety of game mechanics present in
    Super Mario Maker

Goals

Feature: Auto-Tiling

A common quality-of-life feature present in most tile-map editors is auto-tiling. Auto-tiling is a feature that allows users to make visually coherent terrain by selecting the correct tile sprite based on its surrounding tiles and adjusting any neighboring tiles accordingly.

​However, not all terrain requires multiple tile sprites. Some can only have one tile like the spikes tile from Mario
(top-most gif).  It's also possible that you may not want to invest the time to make all 48 sprites required to have completely coherent and visually appealing terrain. You might want to stick to a 16-sprite tile set. That's why I added support for all three kinds of tile sets.

Auto-tiling for a single sprite is pretty straight-forward but it gets slightly complicated to auto-tile for 16 sprites and up. You require bitmasking to achieve auto-tiling. For a 16-sprite tile set, you require a 4-bit bitmask. You assign a successive power of 2, starting from 1, to each cardinal direction (N, S, E, W). All values [0-15] correspond to one of the sprites in the tile set. You calculate the bitmask value by comparing the tile with its neighbors in the cardinal directions and get the appropriate sprite using the final result.

For a 48-sprite tile set, you need to compare with the tiles in the ordinal directions (NE, NW, SE, SW) as well. This means you need an 8-bit bitmask. Starting from 1, each direction is assigned a successive power of 2 and a similar process is followed as it was for the 16-sprite tile set. The problem is that an 8-bit bitmask will gives us values in the range [0-255] but we only have 48 sprites. That is because many bitmask values map to the same sprite. To solve this, you use a look-up table mapping bitmask values to the correct sprite index.

The algorithms referenced above are explained further in detail here. The code below is my implementation of auto-tiling for the editor.

1-Tile Tileset AutoTiling.gif

Auto-tiling for a single sprite tile set

16-Tile Tileset AutoTiling.gif

Auto-tiling for a 16-sprite tile set

48-Tile Tileset AutoTiling.gif

Auto-tiling for a 48-sprite tile set

Function that returns the appropriate UVs for a given tile

Auto-Tiling

Feature: Undo/Redo

A necessity for any editor or tool is having the ability to undo and redo your actions. This allows users to quickly fix mistakes and improves development time and the user experience significantly.

​I used the command pattern detailed here as the basis for my implementation of this feature. The pattern essentially creates an abstract base class for your command with two virtual functions, Undo() and Execute(). You can then inherit from this class to create any kind of specific command and call the Undo() and Execute() functions on it whenever appropriate.

In my implementation, there are two classes, EditorOperation and EditorCommand. An EditorOperation represents each atomic action performed in the editor. Each single tile being painted, for example, is its own separate operation. An EditorCommand is a collection of these operations. If I paint five tiles in one mouse-stroke, that would be considered as a single command containing five operations, one for each tile.

This allowed me to group user-input into chunks that the user finds intuitive and easy to work with. If I paint five tiles in one stroke then I, as a user, consider that as a single action. It would be very tedious if I had to undo five times to undo the entire action.  This implementation also let me combine different kinds of operations into a single command. For example, adding an entity in the level and adding a tile in the level are two distinct operations but I could potentially add an entity and a tile in a single action/mouse-stroke. To undo both at the same time, I would need to group the two operations in a single command which my implementation allows me to do easily.

I maintain two lists to keep track of the commands that I need to undo/redo. To undo or redo a command, I simply iterate over the list of operations contained within a command and call the Undo/Execute function on each operation.

Undo&Redo.gif

Undoing and Redoing a command that moves a selection of objects in the editor

Undo/Redo code for the editor + Undo/Execute code for the editor command

Undo/Redo

Editor Command and Editor Operation classes

Feature: Level Saving/Loading

I wanted users to be able to save and load levels to and fro a file. This would allow users to save their progress and also allow them to share their levels with other users. You just have to share the level save file for your level and someone else should be able to load it.

I had to decide on the format of the save files. I could've used a data encoding method and stored the levels in a binary file. This would make the files compact but it would make it incredibly hard to read the data on your own. If there was a problem in the save file, due to any reason such as data corruption during transfer, then the user would find it very difficult to debug the problem. This problem would also exist during development where it would be difficult for me to test whether my code is saving files correctly or not.

To solve this problem I chose XML files to store my level data. This made the save files easy to read and debug. This also allowed to me to edit levels by simply modifying the XML files instead of having to open the editor for a minor tweak.

 

The size of the files and the time it takes to save the file are the only concerns with this scheme. So far, I have not encountered any major delays in saving or loading the levels and the size of the save files continues to be a few kilobytes. So, for now, I will continue to use XML files to save the levels.

XML Data from a level save file

Level - Saving&Loading.PNG

The level stored in the above XML

Code for saving a level

Level Saving/Loading

Feature: Multi-tool Mouse Input

I wanted the mouse to feel powerful and useful because it occupies an entire hand of the user. I wanted the user to be able to seamlessly switch between tools without feeling interrupted.

Every time I added a new tool to the mouse, the input handling code became messier and harder to debug. I learned that an effective way of solving this problem is by  assigning a state to the mouse. The mouse can never be stateless. This way I was able separate  the code for all the tools in their own methods. It has also made the task of adding new tools easier. 

In my implementation, if the mouse is not performing any actions then it's in the Inspect state. Based on a combination user-input and context about the point where the user clicked.

If the user presses the left mouse button on an empty tile then it will start painting the selected tile. If the user presses the left mouse button on a filled-in tile, then the mouse will start dragging the tile. If the user has the control key pressed then the mouse will start erasing.

Line&Fill&PaintTool.gif

Three different tools for painting tiles - Paint, Line and, Fill Tools

Select&Drag&EraseTool.gif

Select Tool, Drag Tool and, Erase Tool

Input handling code for the various mouse tools

Multi-tool Mouse Input

Retrospectives

What went well
  • Built a strong foundation for editor with essential features necessary for a platforming level editor

What went wrong
  • Poor planning hindered progress and slowed down development.

  • Gameplay is uninteresting and lacks fun game mechanics

Even better ifs
  • Planning with agile methods will improve work flow.

  • Adding more game mechanics will facilitate interesting level design

Retrospective
bottom of page