Level Editor

Oliver  —  1 year, 1 month ago [Edited 1 minute later]
As this game is based around puzzles, one of the requirements for the engine was a strong level editor. One in which I could easily iterate on puzzle designs without much overhead. I knew i wanted to be able to drag entities around, create entities, edit the board, and easily edit the path finding of the AI.

The first step was to create a simple UI system in which I could easily create UI menus in the code, and add actions to buttons. The api looks like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
PushUIElement(UIState, UI_Moveable,  UISet);
        {
            UISet.Type = UISet.Name = "Save Level";
            AddUIElement(GameState->UIState, UI_Button, UISet);
            
            UISet.ValueLinkedToPtr = &GameState->RenderMainChunkType;
            UISet.Name = "Render Main Chunk Type";
            AddUIElement(GameState->UIState, UI_CheckBox, UISet);
        }
PopUIElement(UIState);


Instead of having callback functions for the buttons like in javascript and java, I string match with the type on the button released event:

1
2
3
4
5
6
//OnMouseButtonReleased
case UI_Button: {
    if(DoStringsMatch(InteractEnt->Set.Type, "Save Level")) {
    	SaveLevelToDisk(Memory, GameState, "level1.omm");
    }
} break;


In a past UI implmentation I used C style callbacks for this kind of thing where there are two parameters. One for the function pointer and one for the data.
I found this very hard to use since all functions had to take a generic argument, and we end up switching on types in the end to overcome this.

In this UI implementation I've also used char * for types instead of creating separate enums for each type. For example, to introspect entities I've use the following struct layout for each member:

1
{"u32", "ID", (intptr)&(((entity *)0)->ID)}


I've just stored the type as a string and match it later for the appropriate behavior. I've found this has reduced extra typing a lot.

The next step to get the level editor into a workable state was saving the levels. First I wrote out to a file what data was needed to restore the entities to their correct positions and restore the board, with no real format in mind. It looked like the following:
1
2
3
4
5
6
7
8
//CHUNKS X Y Type MainType
0 0 1 1
1 -1 2 2
-1 1 1 1
//ENTITIES Pos Dim Type ID
1.0 -1.0 1.0 1.0 4 1 2 1 
1.19373952 0.17286156 0.0 0.0 6 2 
-1.13333345 0.0 1.0 1.0 3 3 1 


As I went to save more stuff I quickly noticed the current format of sticking all the entity data on one line wouldn't work, since entities can have arrays of data of variable length. What came apparent then was to store the data as a relational database, in which entities can have one to many relationships, and many to many relationships, all in separate tables. The result looks like this, where each comment line represents a new 'table' within the database.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//CHUNKS X Y Type MainType
0 0 1 1
1 -1 2 2
-1 1 1 1

//ENTITIES Pos Dim Type ID
1.0 -1.0 1.0 1.0 4 1
1.19373952 0.17286156 0.0 0.0 6 2 
-1.13333345 0.0 1.0 1.0 3 3

//CHUNKTYPES_CAN_WALK_ON ID ChunkTypes
1 2 1  
3 1

//CHECKPOINTS ID CheckpointIds
3 1 3


By conceptualizing it as a relational DB, I found it a lot easier to arrange the data. Time will tell if this format is kept. I like this discussion on using a text file per entity to avoid merge conflicts http://the-witness.net/news/2011/...ne-tech-concurrent-world-editing/, and may follow this format in the future, but for now, with only one person editing the world, this will suffice.

Thanks for reading! If you have any suggestions or questions, I would love to hear them.
Log in to comment