Tileson - A Tiled json parser for Modern C++

Hello!
I have no idea if this fits in here, but I’m posting it anyways.
I’ve created a Tiled json parser for Modern C++, and want to share it here in case it would be useful for someone. Any feedback is welcome! Also: If you find a bug, you are very welcome to report it.
If you have any suggestions on how to improve the library, I encourage you to share them :slight_smile:

You can find the parser here:

If this is inappropriate for this forum: Feel free to remove the post.

Sincerelly, Robin.

2 Likes

That is great that you provide another option for C++ developers! I’ve added your library here:

https://doc.mapeditor.org/en/latest/reference/support-for-tmx-maps/#id1

And to this new page, which is still a WIP and not linked from anywhere:

With the above page, I tried to make it easier to maintain and make the information a bit more structured. But I really don’t like working on websites…

1 Like

Thank you very much for adding my library to the list! :slight_smile:
Hope people find it useful!

As it is created to not only be a parser, but to have functionality to make it easier to use the data parsed in a project, I am very open for suggestions to improve the library, as well as keeping it updated with Tiled itself :slight_smile:

2 Likes

Hello SSBM,
Thanks for sharing your work.

Sorry to pollute your topic but I got a trivial question.
Is there any way I can get a drawable object?
Could you eventually explain how?

(I’m a total newbie in Tiled and parsing (by the way the translation of “parser” in french is so foggy I’m not even sure I can use a parser in my project).

(If you have any tutorial in mind I can read or watch, it can be great too)

1 Like

@RacheProu:
This is absolutely on topic, so no pollution at all. I do in fact encourage anyone to ask questions in cases where things are a little unclear :slight_smile:

In short words, “parser” means “reader”. In other words, Tileson reads the data from Tiled maps in json format. You can get a tiled map in .json format by simply exporting it to json, and you will be able to parse/read the data from the map without any problem using C++ with ha modern compiler.

I just created another example in an attempt to help you using data to create drawable objects. Please go ahead and ask me further if you got more questions :slight_smile:

This is a quickly written example just to showcase how you can use some of the data:

    tson::Tileson t;
    tson::Map map = t.parse("../../content/test-maps/ultimate_test.json");

    if(map.getStatus() == tson::Map::ParseStatus::OK)
    {
        //Gets the layer called "Object Layer" from the "ultimate_demo.json map
        tson::Layer *objectLayer = map.getLayer("Object Layer"); //This is an Object Layer

        //Example from an Object Layer.
        if(objectLayer->getType() == tson::Layer::Type::ObjectGroup)
        {
            tson::Object *goomba = objectLayer->firstObj("goomba"); //Gets the first object with this name. This can be any object.

            //If you want to just go through every existing object in the layer:
            for(auto &obj : objectLayer->getObjects())
            {
                tson::Vector2i position = obj.getPosition();
                tson::Vector2i size = obj.getSize();
                tson::Object::Type objType = obj.getObjectType();

                //You may want to check the object type to make sure you use the data right.
            }

            tson::Object::Type objType = goomba->getObjectType();

            /*!
             * tson::Object::Type is defined like this.
             * They are automatically detected based on what kind of object you have created
             * enum class Type : uint8_t
                {
                    Undefined = 0,
                    Object = 1,
                    Ellipse = 2, //<-- Circle
                    Rectangle = 3,
                    Point = 4,
                    Polygon = 5,
                    Polyline = 6,
                    Text = 7,
                    Template = 8
                };
             */

            if (objType == tson::Object::Type::Rectangle)
            {
                tson::Vector2i size = goomba->getSize();
                tson::Vector2i position = goomba->getPosition();

                //If you have set a custom property, you can also get this
                int hp = goomba->get<int>("hp");

                //Using size and position you can can create a Rectangle object by your library of choice.
                //An example if you were about to use SFML for drawing:
                //sf::RectangleShape rect;
                //rect.setSize(sf::Vector2f(size.x, size.y));
                //rect.setPosition(sf::Vector2f(position.x, position.y));
            }
            else if (objType == tson::Object::Type::Polygon)
            {
                for(auto const &poly : goomba->getPolygons())
                {
                    //Set a point on a shape taking polygons
                }
                tson::Vector2i position = goomba->getPosition();
            }
            else if (objType == tson::Object::Type::Polyline)
            {
                std::vector<tson::Vector2i> polys = goomba->getPolylines();
                for(auto const &poly : goomba->getPolylines())
                {

                }
                tson::Vector2i position = goomba->getPosition();
            }
        }

        tson::Layer *tileLayer = map.getLayer("Main Layer"); //This is a Tile Layer.
        tson::Tileset *tileset = map.getTileset("demo-tileset"); //You will also need the tileset used
                                                                       //by the tile map to make sense of everything

        int firstId = tileset->getFirstgid(); //First tile id of the tileset
        int columns = tileset->getColumns(); //For the demo map it is 8.
        int lastId = (tileset->getFirstgid() + tileset->getTileCount()) - 1;

        //Example from a Tile Layer
        //I know for a fact that this is a Tile Layer, but you can check it this way to be sure.
        if(tileLayer->getType() == tson::Layer::Type::TileLayer)
        {
            //pos = position in tile units
            for(auto &[pos, tile] : tileLayer->getTileData()) //Loops through absolutely all existing tiles
            {
                fs::path imagePath;
                std::string pathStr;
                //With this, I know that it's related to the tileset above (though I only have one tileset)
                if(tile->getId() >= firstId && tile->getId() <= lastId)
                {
                    imagePath = tileset->getImagePath();
                    pathStr = imagePath.u8string();
                }

                //Get position in pixel units
                tson::Vector2i position = {std::get<0>(pos) * map.getTileSize().x,std::get<1>(pos) * map.getTileSize().y};
                int tileId = tile->getId();
                //The ID can be used to calculate offset on its related tileset image.
                int offsetX = (tileId % columns) * map.getTileSize().x;
                int offsetY = (tileId / 8) * map.getTileSize().y;

                //Now you can use your library of choice to load the image (like SFML), then set the offset
                //to get the right image representation of the tile.
            }
        }
    }

Edit:
To actually be able to draw any object you will need a library that is able to draw them. Tileson just gives you the data from Tiled to be able to know how to create them.

2 Likes

@SSBMTonberry
Thanks for this more precise than I could imagine.

It’s reassuring to see (at least I understood the base concept :sweat_smile:)

I’m very grateful for this!
I understand all the parts

I promise I will. :grinning:

Thanks!

Hi!

I got an issue with my code.
It works perfectly fine on your example maps.

I did a test map by myself but this loop:

tile becomes a nullptr (on each turn)

Have you ever experienced this issue?
Should my map properties be different ?

Have a good day

@RacheProu: It’s impossible for me to know why you get nullptr without seeing your complete code and what your json map looks like. If you could send your map and your code doing the parsing, I can help you figure out why you get nullptr :slight_smile:

1 Like

@SSBMTonberry : I made you a WeT link with my main.cpp, carte.h and cpp (the code doing the parsing), my map and (just in case) the tileset I use. :slightly_smiling_face:

I’ll check it out and get back to you :slight_smile:

@RacheProu: Your issue was actually due to a bug in Tileson. I have now fixed the bug and added a new release, as the bug was pretty severe, since it would happen in any case where you would have a tile that had nothing attached to it (no properties, collision data etc.). The reason why it has not been discovered until now, is that the demo-tileset actually has collision data on all tiles, which would make them all have a property. I have added unit tests for these cases to make sure they never happen again.

Thank you for reporting this issue! It was a severe error that was important to make a fix for!

If you download Release 1.0.1 of Tileson, your issue should be fixed :slight_smile:

1 Like

@SSBMTonberry
Hello,
I just found your excellent JSON parser, though I’m hopelessly stuck in using it. I’m using the demo code that you provide on your GitHub page, and my problem is I can get the tileset to load, but I’m unsure how to get the map itself to load. Here’s a pastebin of the code I’m using https://pastebin.com/ha60YUBs. Which produces this result:

. This of course makes perfect sense to me as to why it’s doing this, but I want to render the map itself and not the tileset I’m using SFML 2.5.1

@Shadow001: I see that this might be a common question, so I’ll start working on a demo showing how to draw the map using SFML :slight_smile:

A little hint, though, is that you want to use the offsetX and offsetY that is in the example (and in your pastebin) to determine what part of the tileset you need to draw based on the tile. You can get the actual size of a tile from the map itself: map.getTileSize().
This way, you could load parts of the texure you want to display via the sprite:
mSprite.setTextureRect(sf::IntRect(offsetX, offsetY, map.getTileSize().x, map.getTileSize().y)

Also:
In your code, you should not call the mTexture.loadFromFile(pathStr) inside the for-loop, but rather load it once after you have found your tileset. Then you want to use the mSprite.setTexture() once as well, but use setTextureRect() to draw parts of the texture (tiles). You will need to render the sprite for each time you use setTextureRect, though, or else you will end up with only drawing the last one. You will need to make several changes to your code to make this happen.

As I said: I will start working on an example on how to draw the demo-map using SFML, so you can get the idea from there, if you struggle figuring it out :slight_smile:

@SSBMTonberry: You’re welcome. I’m always happy to be useful.
I hope it was not too hard to fix. :slightly_smiling_face:

@RacheProu: Using your code with your map as example, it was pretty easy to spot that something was wrong. After a few debugs and a small analysis on your map, it was not too hard to spot the origin of the error :slight_smile:

Anything that makes the library more stable is greatly appreciated! :slight_smile:

@Shadow001: I now have a basic example commited, using SFML. It is a work in progress , and I have only verified that it compiles on Linux, but you can check out the code here: https://github.com/SSBMTonberry/tileson/tree/master/examples/sfml . If it were to compile in Windows, make sure you put the openal32.dll file in the executable directory. You can find that file under https://github.com/SSBMTonberry/tileson/tree/master/external_libs/libs/win/release/msvc/sfml

It currently only draws images of tile layers, but I will add the rest later. I might be able to expand the example tonight, if I have the time. Also keep in mind that it only shows how to draw what Tileson have parsed on the go. If used in a game you would use the Tileson data to generate game objects once, then have the objects themselves (or a component) do the drawing for you.

@RacheProu: You may find the example useful as well. Check it out :slight_smile:

I also did some improvements on the previous example I’ve posted, as it shows that it was a little flawed :slight_smile:

Forgot to mention: To compile the examples, the CMake flag BUILD_EXAMPLES must be activated. At this point I cannot guarantee that it compiles, as I’ve only tested the compilation on my working environment (Linux) for now.

Checked! Thanks for your example (it’s funny because your drawTileLayer class function is exactly the first modification I did on my previous code).

Once again: Thanks :slightly_smiling_face:

@SSBMTonberry
Thank you so much for posting that demo. I’m still looking at it. I’m on Windows, so I’ll see if I can get it going. Lol I hope you find the time to finish out this demo

Ok I fixed my nullptr exception, now I’m hung up on getting the demo-tileset to load. It keeps giving me an invalid path error

So I lied I’m getting the nullptr exception again here are the screenshots:

@Shadow001: You need to update to the newest version of Tileson: Release 1.0.1. It fixes an issue which probably is causing your crash :slight_smile: