Parsing text files exported from ‘Tiled’ software

This week was the week we decided to use the Tiled map editor software after all. Tiled is a level creator that exports data that can be read by the program to place the correct tiles at the correct places. The main advantage of this is that levels can be edited easily by someone unfamiliar with the code, and coding in general. Our first means of editing levels was to just write numbers manually in a text file, which kind of sucks, but is easy to read with code. We had previously decided against using Tiled because writing code for parsing the exported xml files seemed like more trouble than writing the level data text files manually; but then a wise man taught us how Tiled could also export normal text files.

These text files contain more data than we need and it also seemed like a good idea to split the data and save all we needed in different variables. It was up to me to write the code. The code we had simply read one text file (in which every tile was represented by a single character) and created the level immediately, which means the level was never copied from the text file which would have to be read again if the game was to revert to that level. The new system would also mean that three layers of tiles would be acquired, one for floors, one for walls, and one for all other objects like furniture and pickups. (We will possibly add a layer for patrol pattern nodes.) The text file structure can be seen in the picture below:

level textfile_for blog2

I cropped it for obvious aesthetic reasons; the missing part only contains two more “[layer]”s with different numbers in the map, and type “WALLS” and “Objects”, respectively.

The method LoadLevel() does the following:

  1. Finds the “width=30” line and saves the value to a variable.
  2. Does the same for the height.
  3. Finds the line “data=” and saves the tile map data to a string.
  4. Removes the line breaks from this string.
  5. Saves the string to a variable.
  6. Repeats steps 3 through 5 for the remaining two layers, storing them in different variables.
  7. Creates a new instance of class Level, sending all the acquired level data into its constructor.
  8. Returns a pointer to that instance of Level.

The complete method LoadLevel() with detailed comments can be found below. The main difficulty with this code was to make it look organized, which I think I partially failed. It would have been better to put the text file management in a new class with some operations having their own methods, to be able to reuse them and get a better structure.

 

{
    // All variables we need as parameters for Level
    int index = 1; // "Level 1"
    int width; // Level width, in number of tiles
    int height; // Level height, in number of tiles
    std::string floorData, wallData, objectData; // These will contain the tile map for each level

    // Read the map from the text file and put everything in a string
    std::ifstream levelFile(filepath);

    std::string str, levelData;
    while (std::getline(levelFile, str))
    {
        levelData += str;
        levelData.push_back('\n');
    }

    // Variables that will be needed
    std::size_t foundAt; // Data type "size_t" is a position in an std::string. It's used pretty much like an integer
    std::size_t position; // Will be the main position, only increasing in value since we start from the beginning and never go back
    std::size_t tempPos;
    std::string target;
    std::string tempString;

    //Start parsing levelData
    position = 0; // Beginning of string
    target = "[header]"; // Why don't I just search for "width=" ? I presumably planned a different, shinier approach for the parsing, that would work with slightly different looking text files.
    foundAt = levelData.find(target, position); // Search for the string "[header]" starting at the beginning of the string.
    position = foundAt + target.size() + 1; // Set position to the beginning of the next line. (foundAt is where the target string was found. target.size() is the length of target. + 1 is for going to the next line
    tempPos = levelData.find_first_of("\n", position); // Search for next line break
    tempString = levelData.substr(position, tempPos - position); // Create a temporary substring from the current line

    // More variables needed
    std::string strMain;
    std::string strResult;
    std::size_t pos;

    // Parse the substring for the value
    strMain = tempString; // Copy substring to new string. (Probably because I first planned to do this in a separate method.)
    pos = strMain.find_first_of("0123456789"); // Find the first digit
    while (pos != std::string::npos) // While not at end of string
    {
        strResult += strMain[pos]; // Add the digit
        pos = strMain.find_first_of("0123456789", pos + 1); // Find the next digit
    }

    if (!strResult.empty()) // If there are digits in the string
        width = stoi(strResult); // Save the string as an integer
    strResult.clear(); //Clear strResult for next use

    position = levelData.find("height", position); //Go to the line with "height" in it

    tempPos = levelData.find_first_of("\n", position); //Search for next line break
    tempString = levelData.substr(position, tempPos - position); //Create a temporary substring from the line with "height" in it

    // Here is the same parsing stuff as for width
    strMain = tempString;
    strResult.clear();
    pos = strMain.find_first_of("0123456789");
    while (pos != std::string::npos)
    {
        strResult += strMain[pos];
        pos = strMain.find_first_of("0123456789", pos + 1);
    }

    if (!strResult.empty())
        height = stoi(strResult);

    for (int i = 1; i <= 3; i++) // Once for each layer. This will be increased if we add more layers to the level
    {
        target = "data=";
        foundAt = levelData.find(target, position);
        position = (foundAt + target.size() + 1); // Go to beginning of the tile map

        tempPos = position;
        for (int i = 1; i <= height; i++) // Set tempPos to position, plus height number of lines down. This will encompass the whole map.
        {
            tempPos = levelData.find_first_of("\n", tempPos + 1);
        }

        tempPos++; // Get the last line break, too. Can't remember why...

        tempString = levelData.substr(position, tempPos - position); // Create a temporary string from the map

        // Loop through the map and delete all line breaks, as these will be an annoyance when reading this string and creating tiles based on the numbers
        for (std::size_t found = tempString.find("\n"); found != std::string::npos; found = tempString.find("\n"))
        {
            tempString.erase(found, 1);
        }

        if (i == 1) // If at first layer...
            floorData = tempString; // ...save in floor layer data container
        else if (i == 2) // If at second data layer...
            wallData = tempString; // ...save in wall layer data container
        else if (i == 3) // If at third data layer...
            objectData = tempString; // ... save in object layer data container

        position = tempPos; // Jump to the end of map, so the loop will search for the next "data="
    }

    Level* level = new Level(1, width, height, floorData, wallData, objectData); // Save level data in an instance of the Level class

    return level; // Return the pointer to the newly created Level instance
}
Advertisements

2 thoughts on “Parsing text files exported from ‘Tiled’ software

  1. Hello John! It is nice and very intresting to see how you have tackled the map layout problem. You describe well what you made. A way to edit and create levels easily and pretty smoothly from a text file which can changed on a whim from what is required of the level per say. It was a very nice touch to include the “picture”. It explained a lot and you could see what it actually is. As a programmer this was easy to understand but I feel like it might be a bit difficult for someone with less knowledege to understand what is going on. But ignoring that you are describing what you have made very well!
    It was also very easy to understand why you created this file reading system and from reading the post it was obvious.
    How you have made it could have been explained a bit better, it was easy to understand what you have made and the result was very clear, but I got a bit intrested in how you managed to create it and how you designed it.
    Anyway, it was a fun read and it was quite technical. The picture was great for a programmer to understand but I wonder if a graphics artists would have understod it as easily. Good luck with the beta! 🙂

    Like

  2. Nice bloggpost.

    It’s great the see that you are finding new and useful ways of making your engine more effective and easier to handle for the entire team. I remember our group having a talk about making some type of level editor with graphics. This is not really that but a step in the right direction I feel like. For us designing levels in text documents has been, and continues to be a real hassle. Maybe it’s a bit less irritating with your game (we’re doing the Bat game) but jumping between editor and gameplay having to remember what to change is maybe not the optimal way of working with levels.

    Are you doing some type of sketches of the levels first or are you just designing them straight up in a text editor?

    It seems like you found a pretty smart ways of handling the amount of information you store in the text files. I can imagine it gives you guys a more flexible way of making quick changes to level layouts. I kind of feel like it’s too late for us but something like this would actually be pretty usfull for our game. Let’s just say we’ll think about it.

    Thanks for the inspiriation.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s