Lazy Foo' Productions


Making a Level Editor

Last Updated 12/27/11
Having to hard code your levels is a pain. Fortunately you can make and save a level with a level editor. Here you'll get the basics on how to make one.
In this article we're going to be using the tiling engine from the Tiling tutorial. This article assumes you've read it, so if you haven't already, you should read it now.

When it comes to level editors, there's 3 basic things you need. To demonstrate the first two things, I've made this video:

This is a demo of the debug mode for Sonic the Hedgehog for the Sega Genesis:
The code to activate it is (at the title screen) press C, C, Up, Down, Left, Right, Hold A, Start and keep holding A 'til the first level appears.

In this video I goof around with the debug mode. The debug mode is not a full level editor but it has two features you want to pay attention to: the ability to choose a game object and place it where you want. With our level editor, we're going to be able to choose a tile and place it where we want on our tile map.

Now what is this missing to be able to be a full fledged level editor? Simple, you can't save and load what you made. We already covered how to load a tile map in the tiling tutorial, and we'll cover saving in this article.

Now let's get started with our level editor.
void set_camera() { //Mouse offsets int x = 0, y = 0; //Get mouse offsets SDL_GetMouseState( &x, &y ); //Move camera to the left if needed if( x < TILE_WIDTH ) { camera.x -= 20; } //Move camera to the right if needed if( x > SCREEN_WIDTH - TILE_WIDTH ) { camera.x += 20; } //Move camera up if needed if( y < TILE_WIDTH ) { camera.y -= 20; } //Move camera down if needed if( y > SCREEN_HEIGHT - TILE_WIDTH ) { camera.y += 20; }
First thing we need to be able to do is move through the tile map. In the tiling tutorial, we used the dot to set camera focus. Here we're going to use the mouse to move the camera.

We set the camera by first getting the mouse offsets using SDL_GetMouseState(). If the mouse is at the top edge of the screen the camera will scroll up, if the camera is on the right edge of the screen it will scroll to the right, etc, etc.
//Keep the camera in bounds. if( camera.x < 0 ) { camera.x = 0; } if( camera.y < 0 ) { camera.y = 0; } if( camera.x > LEVEL_WIDTH - camera.w ) { camera.x = LEVEL_WIDTH - camera.w; } if( camera.y > LEVEL_HEIGHT - camera.h ) { camera.y = LEVEL_HEIGHT - camera.h; } }
And of course after we move the camera we have to keep it in bounds.
void put_tile( Tile *tiles[], int tileType ) { //Mouse offsets int x = 0, y = 0; //Get mouse offsets SDL_GetMouseState( &x, &y ); //Adjust to camera x += camera.x; y += camera.y;
Here's is the function we're going to call when we click the mouse to place a tile. It takes in a tile set and the type of tile we're going to place.

At the top of put_tile() we get the mouse offsets and then we add camera offsets to them. The reason we do this is because when we call SDL_GetMouseState(), we get the mouse offsets with respect to the screen. We want the mouse offsets relative to the whole level.

So we add the camera offsets to the mouse offsets because the postion in the level is the mouse offsets plus how much the camera has scrolled.
//Go through tiles for( int t = 0; t < TOTAL_TILES; t++ ) { //Get tile's collision box SDL_Rect box = tiles[ t ]->get_box(); //If the mouse is inside the tile if( ( x > box.x ) && ( x < box.x + box.w ) && ( y > box.y ) && ( y < box.y + box.h ) ) { //Get rid of old tile delete tiles[ t ]; //Replace it with new one tiles[ t ] = new Tile( box.x, box.y, tileType ); } } }
Then we go through the tiles and find which one the user clicked on. Then we delete the old tile and replace it with the type of tile we want.
bool set_tiles( Tile *tiles[] ) { //The tile offsets int x = 0, y = 0; //Open the map std::ifstream map( "lazy.map" ); //If the map couldn't be loaded if( map == NULL ) { //Initialize the tiles for( int t = 0; t < TOTAL_TILES; t++ ) { //Put a floor tile tiles[ t ] = new Tile( x, y, t % ( TILE_BLUE + 1 ) ); //Move to next tile spot x += TILE_WIDTH; //If we've gone too far if( x >= LEVEL_WIDTH ) { //Move back x = 0; //Move to the next row y += TILE_HEIGHT; } } }
Here's the top set_tiles() function. It's very similar to the one in the tiling tutorial.

What's changed? In the tiling tutorial when there was no tile map file, the program just quit. In this program, it's ok not to have a tile map already made because this application is supposed to make new tile maps.

So if there's no map file, we create a tile set made of floor tiles. It puts a red tile, then a green one, then a blue one, and then repeats itself until it the tile map is full.
else { //Initialize the tiles for( int t = 0; t < TOTAL_TILES; t++ ) { //Determines what kind of tile will be made int tileType = -1; //Read tile from map file map >> tileType; //If there was a problem in reading the map if( map.fail() == true ) { //Stop loading map map.close(); return false; } //If the number is a valid tile number if( ( tileType >= 0 ) && ( tileType < TILE_SPRITES ) ) { tiles[ t ] = new Tile( x, y, tileType ); } //If we don't recognize the tile type else { //Stop loading map map.close(); return false; } //Move to next tile spot x += TILE_WIDTH; //If we've gone too far if( x >= LEVEL_WIDTH ) { //Move back x = 0; //Move to the next row y += TILE_HEIGHT; } } //Close the file map.close(); } return true; }
The rest of the function is pretty much exactly the same as in the tiling tutorial.

It goes through the file, reads a number, then places a tile based on the number in the map file and keeps reading from the map file until it's placed all the tiles.
void save_tiles( Tile *tiles[] ) { //Open the map std::ofstream map( "lazy.map" ); //Go through the tiles for( int t = 0; t < TOTAL_TILES; t++ ) { //Write tile type to file map << tiles[ t ]->get_type() << " "; } //Close the file map.close(); }
Saving a tile map we've made is easy. All we do is just go through the tile set and write each of the tile's type to the file.
int main( int argc, char* args[] ) { //Quit flag bool quit = false; //Current tile type int currentType = TILE_RED; //The tiles that will be used Tile *tiles[ TOTAL_TILES ]; //The frame rate regulator Timer fps; //Initialize if( init() == false ) { return 1; } //Load the files if( load_files() == false ) { return 1; } //Clip the tile sheet clip_tiles(); //Set the tiles if( set_tiles( tiles ) == false ) { return 1; }
Here's the top of the main() function. It's pretty much the same as with the tiling tutorial only now we have the "currentType" variable, which keeps track of the current tile we're using.
//While there's events to handle while( SDL_PollEvent( &event ) ) { //When the user clicks if( event.type == SDL_MOUSEBUTTONDOWN ) { //On left mouse click if( event.button.button == SDL_BUTTON_LEFT ) { //Put the tile put_tile( tiles, currentType ); }
Here's the beginning of the event handling loop.

When the user presses the left mouse button, we call our put_tile() function to place a tile.
//On mouse wheel scroll else if( event.button.button == SDL_BUTTON_WHEELUP ) { //Scroll through tiles currentType--; if( currentType < TILE_RED ) { currentType = TILE_TOPLEFT; } //Show the current tile type show_type( currentType ); } else if( event.button.button == SDL_BUTTON_WHEELDOWN ) { //Scroll through tiles currentType++; if( currentType > TILE_TOPLEFT ) { currentType = TILE_RED; } //Show the current tile type show_type( currentType ); } } //If the user has Xed out the window if( event.type == SDL_QUIT ) { //Quit the program quit = true; } }
When the user scrolls up or down on the mouse wheel, we change the current tile type accordingly and update the caption to show what's the current tile type.

At then end of the event loop, we handle a user quit like always.
void show_type( int tileType ) { switch( tileType ) { case TILE_RED: SDL_WM_SetCaption( "Level Designer. Current Tile: Red Floor", NULL ); break; case TILE_GREEN: SDL_WM_SetCaption( "Level Designer. Current Tile: Green Floor", NULL ); break; case TILE_BLUE: SDL_WM_SetCaption( "Level Designer. Current Tile: Blue Floor", NULL ); break; case TILE_CENTER: SDL_WM_SetCaption( "Level Designer. Current Tile: Center Wall", NULL ); break; case TILE_TOP: SDL_WM_SetCaption( "Level Designer. Current Tile: Top Wall", NULL ); break; case TILE_TOPRIGHT: SDL_WM_SetCaption( "Level Designer. Current Tile: Top Right Wall", NULL ); break; case TILE_RIGHT: SDL_WM_SetCaption( "Level Designer. Current Tile: Right Wall", NULL ); break; case TILE_BOTTOMRIGHT: SDL_WM_SetCaption( "Level Designer. Current Tile: Bottom Right Wall", NULL ); break; case TILE_BOTTOM: SDL_WM_SetCaption( "Level Designer. Current Tile: Bottom Wall", NULL ); break; case TILE_BOTTOMLEFT: SDL_WM_SetCaption( "Level Designer. Current Tile: Bottom Left Wall", NULL ); break; case TILE_LEFT: SDL_WM_SetCaption( "Level Designer. Current Tile: Left Wall", NULL ); break; case TILE_TOPLEFT: SDL_WM_SetCaption( "Level Designer. Current Tile: Top Left Wall", NULL ); break; }; }
If you were wondering how the show_type() function works, it's a simple switch block that shows the current tile type in the caption.
//Set the camera set_camera(); //Show the tiles for( int t = 0; t < TOTAL_TILES; t++ ) { tiles[ t ]->show(); } //Update the screen if( SDL_Flip( screen ) == -1 ) { return 1; } //Cap the frame rate if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ) { SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() ); } }
Here's the rest of the main loop. Nothing much to see here.
//Save the tile map save_tiles( tiles ); //Clean up clean_up( tiles ); return 0; }
Lastly, we save the tile map we made and do our clean up at the end of the main() function.
So that's it for the level editor made for this article. The next section of the article assumes you have a good handle on inheritance and polymorphism. As I said in the state machines article, if you don't know these OOP concepts already you should learn them if you want to make anything complex in C++.

The tiling engine we use here is a simple one. We only have tile objects and their only difference is their sprite and being a floor or a wall type. What about more complex level engines?

A common way to organize your different level object classes is to have a base class and have your different classes inherit from it.
class GameObject { //Stuff }; class Wall : public GameObject { //Stuff }; class Door : public GameObject { //Stuff }; class ThisThing : public GameObject { //Stuff }; class ThatThing : public GameObject { //Stuff };
This would be a way to organize your classes for your game. We have a base game object class and we have our game objects inherit from it.
void put_tile( std::vector<GameObject*> &level, int objectType ) { //Mouse offsets int x = 0, y = 0; //Get mouse offsets SDL_GetMouseState( &x, &y ); //Adjust to camera x += camera.x; y += camera.y; //Place object switch( objectType ) { case LVLOBJ_THIS: level.push_back( new ThisThing( x, y ) ); break; case LVLOBJ_THAT: level.push_back( new ThatThing( x, y ) ); break; } }
Here's what a placement function might look like. It simply adds in a new game object to the level at the place the user specified.

Well placing new objects in the level was easy enough but how do you save the objects?
void ThisThing::write_info( std::ofstream &save ) { //Write type save << LVLOBJ_THIS << " "; //Write attributes save << x << " "; save << y << " "; save << maxHealthPoints << " "; save << weapon << " "; save << etc << " "; }
What you can do is have a save function that goes through the level objects, then give your objects a function that writes their attributes to a file.

Here we have a function that takes in a reference to a save file. First we write the object type to the file, and after that we write the various attributes to the file.

Writing the level objects was easy enough, but what about reading them?
//Go through file while( file.eof()! ) { //Determines what kind of object will be made int objectType = -1; //Read level object type from level file file >> objectType; //If there was a problem in reading the level file if( map.fail() == true ) { //Stop loading level map.close(); return false; } //Place object switch( objectType ) { case LVLOBJ_THIS: level.push_back( new ThisThing( file ) ); break; case LVLOBJ_THAT: level.push_back( new ThatThing( file ) ); break; } }
When we wrote the level object to the file, we had each object write a set of attributes to a file. The first thing in each of the sets was the type of object.

When reading in objects we would read the first character to determine what kind of object the attribute set belongs to. Then we add the proper object to the level and pass a reference of the file to the constructor of the new object.
ThisThing::ThisThing( std::ifstream &load ) { //Get attributes load >> x; load >> y; load >> maxHealthPoints; load >> weapon; load >> etc; }
Now the constructor can continue reading the file and get its attributes. Once this object is constructed, the loading function will continue reading more objects.

Now what if you want to edit objects while in the editor?
while( SDL_PollEvent( &event ) ) { //When the user clicks if( event.type == SDL_MOUSEBUTTONDOWN ) { //On left mouse click if( event.button.button == SDL_BUTTON_LEFT ) { //Select an object select_level_object( level, selectedObject ); } } //Edit level object if( selectedObject != NULL ) { selectedObject->handle_edit(); } }
//Show edit interface if( selectedObject != NULL ) { selectedObject->show_edit(); }
You could give your game objects functions that allow you to edit their attributes.

You would need something to select the game objects, a function to handle events to edit the object's attributes, and a function that shows the attribute editor.

The above two segments of psuedo code show what these might look like.
void select_level_object( std::vector &level, GameObject* &selected ) { //Mouse offsets int x = 0, y = 0; //Get mouse offsets SDL_GetMouseState( &x, &y ); //Adjust to camera x += camera.x; y += camera.y; //Don't want to point to old selected object. selected = NULL; //Go through level objects for( int o = 0; o < level.size(); o++ ) { //If the mouse touches a level object if( mouse_touches( level[ o ] ) == true ) { selected = level[ o ]; } } } void ThisThing::handle_edit() { //Handle GUI Objects hpEditor.handle_events(); weaponEditor.handle_events(); etcEditor.handle_events(); } void ThisThing::show_edit() { //Show GUI Objects hpEditor.show(); weaponEditor.show(); etcEditor.show(); }
This is what the object selecting and attribute editing functions might look like. Now that we're getting to into GUIs, I would recommend using a GUI library if you're going to do any complex GUIs. You could roll your own GUIs like in this psuedo code, but some people think it's like pulling teeth. It's your level designer so it's up to you.

There is other stuff we could cover, like using binary file IO as opposed to ASCII like we did here, file selectors, or adding all sort of other features, but you have the fundamentals. The hardest part is getting off the ground, so the rest you should be able to figure out with a little creative thinking and googling.
Download the media and source code for this article here.