Giter Site home page Giter Site logo

ssbmtonberry / tileson Goto Github PK

View Code? Open in Web Editor NEW
185.0 6.0 29.0 50.69 MB

A modern and helpful cross-platform json-parser for C++, used for parsing Tiled maps.

License: BSD 2-Clause "Simplified" License

CMake 2.10% C++ 91.27% Batchfile 0.01% Shell 0.08% CSS 0.27% HTML 0.01% Makefile 0.07% Objective-C 0.74% Objective-C++ 2.34% Rich Text Format 0.01% GLSL 0.07% C 3.07%

tileson's Introduction

Windows (MSVC) Linux (GCC) Linux (Clang) MacOS (Clang) pages-build-deployment

Tileson

Tileson is a modern and helpful cross-platform json-parser for C++, used for parsing Tiled maps.

Tileson utilizes modern C++ (C++17) to create a stable, safe and helpful, but fast, parser for Tiled maps. Including classes and functions to make it easier to use the Tiled data in your game. Tileson supports Tiled maps up to version 1.10.2, but will probably be able to parse maps made with newer versions of Tiled just as well.

Be sure to take a look at the release notes to see what's new!

Tileson is header-only

This means that all you need is one file, tileson.hpp to have Tileson going in your project! The single-file is generated using only ~7000 lines of code with everything included. There is also a tileson_min.hpp where no Json parser is bundled. See the extras folder for supported Json backends.

You may alternatively copy the include directory and all its contents if you want to have every component in their own file. This will probably be less heavy on your IDE, but you will still only need to include the tileson.h file in the top level.

Content:

Documentation

There is a Doxygen generated documentation of Tileson that can be found HERE

IMPORTANT: Tileson requires that everything it needs in a map is embedded into it, to be able to resolve their related objects (with the exception of external Tilesets, which is supported in json format since v1.3.0). Maps having dependencies to external objects etc. will not work properly.

How to contribute

You are free to post any issue requesting new features, reporting bugs or asking questions at any time. If you want to contribute in the development of Tileson, make sure you read the CONTRIBUTION GUIDELINES before you start doing anything.

Unreleased features available in the master-branch

  • Added support for Image Collection Tilesets (#117, #30) - Thanks to twje
  • Fix: Demo - Tiles rotated to 90 and 270 degrees display incorrectly (#116) - Thanks to twje
  • Generated tiles are now considering local IDs (#112, #114) - Thanks to twje
  • Tile::getDrawingRect() is now based on tile grid size (previously map grid size) (#109) - Thanks to tmpsantos
  • A new function Tileset::getFullImagePath() to retrieve a full path to an image based on the loaded map. Tileset::getImagePath() still returns a relative path. (#107) - Thanks to tmpsantos
  • Attributes of classes set as properties return zero where overridden (#105) - Thanks to tmpsantos

What is new in v1.4.0

  • Fixed bug where template objects did not correctly override properties (#100) - Thanks to jpeletier
  • Fixed bugs related to not being able to resolve TiledEnums in certain contexts (#98)
  • Fix: Only include external_libs folder if examples or tests are required (#96) - Thanks to Laguna1989
  • Tests are now stricter and treats warnings as errors (#90) - Thanks to dmlary
  • CI improvements: Added MacOS, separated CI by system and added Clang 12 and 13 support on Linux (#88)
  • Fixed some Apple Clang 13 compile warnings (#84) - Thanks to dmlary
  • Added quom as amalgamate tool for OSX (#82) - Thanks to dmlary
  • Added missing properties to tson::Text (#75)
  • Tiled 1.9 support (#68)
  • Tiled 1.8 support (#60)
  • C++20 support (#53) - Thanks to gamecoder-nz
  • Updated Catch2 to support GCC 11.2 (#59)
  • Tile properties should now be properly loaded when using multiple tilesets. (#54) - Thanks to Laguna1989
  • Now using Github Actions instead of Travis for CI (#50) - Thanks to Laguna1989
  • Added missing virtual destructor to IJson and IDecompressor. (#47) - Thanks to matthew-nagy

Tiled features not yet supported

  • Isometric maps are untested, and most likely does not work 100% (#97)

What is Tiled?

Tiled is a general purpose map editor developed by Thorbjørn Lindeijer. Tiled is perfect if you want to create any map for 2D games, and is very popular. Many commercial games have used it as their goto map editor. A few popular games that have used Tiled: Shovel Knight, Axiom Verge and ScubaDiver.

alt text

Tiled can be found here:

How to parse Tiled maps

Parsing a Tiled json

#include "tileson.hpp"

//Tileson uses an alias fs for std::filesystem.
int main()
    tson::Tileson t;
    std::unique_ptr<tson::Map> map = t.parse(tson_files::_ULTIMATE_TEST_JSON, tson_files::_ULTIMATE_TEST_JSON_SIZE);

    if(map->getStatus() == tson::ParseStatus::OK)
    {
        //Get color as an rgba color object
        tson::Colori bgColor = map->getBackgroundColor(); //RGBA with 0-255 on each channel

        //This color can be compared with Tiled hex string
        if (bgColor == "#ffaa07")
            printf("Cool!");

        //Or you can compare them with other colors
        tson::Colori otherColor{255, 170, 7, 255};
        if (bgColor == otherColor)
            printf("This works, too!");

        //You can also get the color as float, transforming values if they are already in their int form, from max 255 to 1.f
        tson::Colorf bgColorFloat = bgColor.asFloat();

        //Or the other way around
        tson::Colori newBg = bgColorFloat.asInt();

        //You can loop through every container of objects
        for (auto &layer : map->getLayers())
        {
            if (layer.getType() == tson::LayerType::ObjectGroup)
            {
                for (auto &obj : layer.getObjects())
                {
                    //Just iterate through all the objects
                }
                //Or use these queries:

                //Gets the first object it finds with the name specified
                tson::Object *player = layer.firstObj("player"); //Does not exist in demo map->..

                //Gets all objects with a matching name
                std::vector<tson::Object> enemies = layer.getObjectsByName("goomba"); //But we got two of those

                //Gets all objects of a specific type
                std::vector<tson::Object> objects = layer.getObjectsByType(tson::ObjectType::Object);

                //Gets an unique object by its name.
                tson::Object *uniqueObj = layer.getObj(2);
            }
        }

        //Or get a specific object if you know its name (or id)
        tson::Layer *layer = map->getLayer("Main Layer");
        tson::Tileset *tileset = map->getTileset("demo-tileset");
        tson::Tile *tile = tileset->getTile(1); //Tileson tile-IDs starts with 1, to be consistent
        // with IDs in data lists. This is in other words the
        //first tile.

        //For tile layers, you can get the tiles presented as a 2D map by calling getTileData()
        //Using x and y positions in tile units.
        if(layer->getType() == tson::LayerType::TileLayer)
        {
            //When the map is of a fixed size, you can get the tiles like this
            if(!map->isInfinite())
            {
                std::map<std::tuple<int, int>, tson::Tile *> tileData = layer->getTileData();

                //Unsafe way to get a tile
                tson::Tile *invalidTile = tileData[{0, 4}]; // x:0,  y:4  - There is no tile here, so this will be nullptr.
                                                                   // Be careful with this, as it expands the map with an ID of {0,4} pointing
                                                                   // to a nullptr...

                //Individual tiles should be retrieved by calling the safe version:
                tson::Tile *safeInvalid = layer->getTileData(0, 5); //Another non-existent tile, but with safety check.
                                                                         //Will not expand the map with a nullptr

                tson::Tile *tile1 = layer->getTileData(4, 4);       //x:4,  y:4  - Points to tile with ID 1 (Tiled internal ID: 0)
                tson::Tile *tile2 = layer->getTileData(5, 4);       //x:5,  y:4  - Points to tile with ID 3 (Tiled internal ID: 2)
                tson::Tile *tile3 = layer->getTileData(8, 14);      //x:8,  y:14 - Points to tile with ID 2 (Tiled internal ID: 1)
                tson::Tile *tile4 = layer->getTileData(17, 5);      //x:17, y:5  - Points to tile with ID 5 (Tiled internal ID: 4)

                //New in v1.2.0
                //You can now get tiles with positions and drawing rect via tson::TileObject
                //Drawing rects are also accessible through tson::Tile.
                tson::TileObject *tileobj1 = layer->getTileObject(4, 4);
                tson::Vector2f position = tileobj1->getPosition();
                tson::Rect drawingRect = tileobj1->getDrawingRect();

                //You can of course also loop through every tile!
                for (const auto &[id, tile] : tileData)
                {
                    //Must check for nullptr, due to how we got the first invalid tile (pos: 0, 4)
                    //Would be unnecessary otherwise.
                    if(tile != nullptr)
                        int tileId = tile->getId(); //A bit verbose, as this is the same as id from the key, but you get the idea.
                }
            }
        }

        //If an object supports properties, you can easily get a property value by calling get<T>() or the property itself with getProp()
        int myInt = layer->get<int>("my_int");
        float myFloat = layer->get<float>("my_float");
        bool myBool = layer->get<bool>("my_bool");
        std::string myString = layer->get<std::string>("my_string");
        tson::Colori myColor = layer->get<tson::Colori>("my_color");

        fs::path file = layer->get<fs::path>("my_file");

        tson::Property *prop = layer->getProp("my_property");
    }
    else //Error occured
    {
        std::cout << map->getStatusMessage();
    }

    return 0;
}

Another quick example to showcase how to get data that can be used to produce drawable objects:

tson::Tileson t;
std::unique_ptr<tson::Map> map = t.parse(fs::path("./path/to/map.json"));

if(map->getStatus() == tson::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::LayerType::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::ObjectType objType = obj.getObjectType();

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

        tson::ObjectType objType = goomba->getObjectType();

        /*!
         * tson::ObjectType 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::ObjectType::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::ObjectType::Polygon)
        {
            for(auto const &poly : goomba->getPolygons())
            {
                //Set a point on a shape taking polygons
            }
            tson::Vector2i position = goomba->getPosition();
        }
        else if (objType == tson::ObjectType::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.

    //You can get your tileset like this, but in v1.2.0
    //The tiles themselves holds a pointer to their related tileset.
    tson::Tileset *tileset = map->getTileset("demo-tileset");

    //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::LayerType::TileLayer)
    {
        //pos = position in tile units
        for(auto &[pos, tileObject] : tileLayer->getTileObjects()) //Loops through absolutely all existing tileObjects
        {
            tson::Tileset *tileset = tileObject.getTile()->getTileset();
            tson::Rect drawingRect = tileObject.getDrawingRect();
            tson::Vector2f position = tileObject.getPosition();

            //Here you can determine the offset that should be set on a sprite
            //Example on how it would be done using SFML (where sprite presumably is a member of a generated game object):
            //sf::Sprite *sprite = storeAndLoadImage(tileset->getImage().u8string(), {0, 0});
            //if (sprite != nullptr)
            //{
            //    sprite->setTextureRect({drawingRect.x, drawingRect.y, drawingRect.width, drawingRect.height});
            //    sprite->setPosition({position.x, position.y});
            //    m_window.draw(*sprite);
            //}
        }
    }
}

Parsing worlds

  • Tileson now supports Tiled worlds. These contains a collection of several maps that can be tied together, but the files themselves must be parsed separately using Tileson. (See examples to get a full idea how worlds works):
tson::Tileson t;
tson::World world; 
world.parse(fs::path("path/to/you/file.world"));

//world.get("w1.json") can be used to get a specific map

for(const auto &data : world.getMapData())
{
    std::unique_ptr<tson::Map> map = t.parse(fs::path(world.getFolder() / data.fileName));
    //...
}

Parsing Tiled-projects

  • Tileson now supports Tiled projects. These contains all map and world data, but the files themselves must be parsed separately using Tileson. (See examples to get a full idea how projects works).
tson::Tileson t;
tson::Project project; 
bool ok = project.parse(fs::path("path/to/you/file.tiled-project"));

for(const auto &folder : m_project.getFolders())
{
    // You can check if a project folder contains a world with -> folder.hasWorldFile()
    // If it does, you can get the world data with -> folder.getWorld()
    for(const auto &file : folder.getFiles())
    {
        std::unique_ptr<tson::Map> map = t.parse(fs::path(folder.getPath() / file.filename()));
        //...
    }
}

Using Tiled's class and enum types in Tiled-projects

fs::path pathToUse = fs::path("path/to/project.tiled-project");
tson::Project project{pathToUse};
auto folderFiles = project.getFolders().at(0).getFiles();

for(fs::path &f: folderFiles)
{
    fs::path path = project.getFolders().at(0).getPath() / f.filename();
    std::string filename = f.filename().generic_string();
    if(filename == "map1.json")
    {
        tson::Tileson t{&project};
        std::unique_ptr<tson::Map> m = t.parse(path);
        tson::Layer *objectLayer = m->getLayer("Da Object Layer");

        //Get class from object
        tson::TiledClass *objectClass = objectLayer->firstObj("TestObject")->getClass(); 
        
        //Asserts as example how to use members
        REQUIRE(objectClass != nullptr);
        REQUIRE(objectClass->getName() == "Enemy");
        REQUIRE(objectClass->get<int>("hp") == 10);
        REQUIRE(objectClass->get<std::string>("name") == "Galderino");

        //Get class from tile
        tson::Tile *tile = m->getTileset("demo-tileset")->getTile(1);
        tson::TiledClass *tileClass = tile->getClass();

        //Example how to get member of different types with asserts
        REQUIRE(objectClass->getMember("Age")->getType() == tson::Type::Int);
        REQUIRE(objectClass->getMember("Age")->getValue<int>() == 49);
        REQUIRE(objectClass->get<int>("Age") == 49);
        REQUIRE(objectClass->getMember("CanDestroy")->getType() == tson::Type::Boolean);
        REQUIRE(objectClass->get<bool>("CanDestroy"));
        REQUIRE(objectClass->getMember("ExtraFile")->getType() == tson::Type::File);
        REQUIRE(objectClass->get<fs::path>("ExtraFile") == fs::path("../ultimate_test.json"));
        REQUIRE(objectClass->getMember("MoneyInBag")->getType() == tson::Type::Float);
        REQUIRE(tson::Tools::Equal(objectClass->get<float>("MoneyInBag"), 16.9344f));
        REQUIRE(objectClass->getMember("MyObject")->getType() == tson::Type::Object);
        REQUIRE(objectClass->get<uint32_t>("MyObject") == 39);
        REQUIRE(objectClass->getMember("Name")->getType() == tson::Type::String);
        REQUIRE(objectClass->get<std::string>("Name") == "James Testolini");
        REQUIRE(objectClass->getMember("ShoeColor")->getType() == tson::Type::Color);
        tson::Colori color = objectClass->get<tson::Colori>("ShoeColor");
        REQUIRE(color == "#ff069504");
        REQUIRE(color.a == 0xff);
        REQUIRE(color.r == 0x06);
        REQUIRE(color.g == 0x95);
        REQUIRE(color.b == 0x04);

        //Example of different enum properties stored within objects
        //Numeric and string based enums with and without flag properties
        tson::Object *enumObj = objectLayer->firstObj("TestObjectEnum");
        tson::TiledClass *objectClassEnum = enumObj->getClass(); //Object is changed from default values
        tson::TiledClass *tileClassEnum = tileClass;
        
        REQUIRE(enumObj->getProp("num_enum") != nullptr);
        tson::EnumValue objPropNumEnum = enumObj->get<tson::EnumValue>("num_enum");
        REQUIRE(enumObj->getProp("num_enum_flags") != nullptr);
        tson::EnumValue objPropNumEnumFlags = enumObj->get<tson::EnumValue>("num_enum_flags");
        REQUIRE(enumObj->getProp("str_enum") != nullptr);
        tson::EnumValue objPropStrEnum = enumObj->get<tson::EnumValue>("str_enum");
        REQUIRE(enumObj->getProp("str_enum_flags") != nullptr);
        tson::EnumValue objPropStrEnumFlags = enumObj->get<tson::EnumValue>("str_enum_flags");


        REQUIRE(objPropNumEnum.getValue() == 3); 
        REQUIRE(objPropNumEnum.getValueName() == "GetNumber"); 
        REQUIRE(objPropNumEnumFlags.getValue() == 9); 
        //Flags enums (numeric and string) may use custom enum classes, as long as they have applied flags logic applied to them. See details how this can be achieved below this code example
        REQUIRE(objPropNumEnumFlags.hasFlag(tson::TestEnumNumberFlags::HasCalculatorFlag | tson::TestEnumNumberFlags::HasInvisibilityFlag)); 
        REQUIRE(objPropStrEnum.getValue() == 2); 
        REQUIRE(objPropStrEnum.getValueName() == "DeletePlayer"); 
        REQUIRE(objPropStrEnumFlags.getValue() == 6); 
        REQUIRE(objPropStrEnumFlags.hasFlag(tson::TestEnumStringFlags::HasJobFlag | tson::TestEnumStringFlags::HasHouseFlag));

        //Another example with flags more in depth
        tson::EnumValue numEnumC2 = someClass.getMember("NumFlag")->getValue<tson::EnumValue>();
        tson::EnumValue strEnumC2 = someClass.getMember("StrFlag")->getValue<tson::EnumValue>(); //Not used here, but will work in the same way

        REQUIRE(someClass.getMember("NumFlag")->getType() == tson::Type::Enum);
        REQUIRE(numEnumC2.getValue() == 10);
        REQUIRE(numEnumC2.hasFlag(tson::TestEnumNumberFlags::HasBombFlag | tson::TestEnumNumberFlags::HasInvisibilityFlag)); //Has both these flags - OK
        REQUIRE(numEnumC2.hasFlag(tson::TestEnumNumberFlags::HasBombFlag)); // Has this flag - OK
        REQUIRE(numEnumC2.hasFlag(tson::TestEnumNumberFlags::HasInvisibilityFlag)); // Has this flag - OK
        REQUIRE(numEnumC2.hasAnyFlag(tson::TestEnumNumberFlags::HasBombFlag | tson::TestEnumNumberFlags::HasHumorFlag)); //hasAnyFlag is okay as long as one of the flags here are set
        REQUIRE(!numEnumC2.hasFlag(tson::TestEnumNumberFlags::HasHumorFlag)); //Doesn't have this flag - OK
    }
}

The enum bitflags used in the examples above, uses a macro defined in include/common/EnumBitflags.hpp to be able to use them for bitflag checks. The only restriction this macro has, it's that it requires the tson namespace of any enums using it. With that in mind, here is an example how to create a flags enum:

namespace tson
{
    enum class ExampleFlags : uint32_t
    {
        None = 0,
        Big = 1 << 0,
        Slippery = 1 << 1,
        Funny = 1 << 2,
        Lazy = 1 << 3,
        All = Big | Slippery | Funny | Lazy
    };
}

TILESON_ENABLE_BITMASK_OPERATORS(ExampleFlags)

If you need the flags to be another namespace: Feel free to just steal the code and modify it for you own use.

Parsing LZMA compressed maps

Tileson supports reading fully LZMA compressed Tiled-maps. LZMA compression can greatly reduce your file size, and does not take much longer to load compared to regular maps. This requires another single-header library PocketLzma to be included, but it does not require much work. As an example, the ultimate_test.json map gets reduced from 68,6 KiB to 2,4 KiB when LZMA compressed. Here is a small code example how to read lzma-compressed maps:

#define POCKETLZMA_LZMA_C_DEFINE //Must be defined once in a source (.cpp)-file before 
#include "pocketlzma.hpp" //Must be declared BEFORE tileson.hpp

#include "tileson.hpp"
int main(int argc, char **argv)
{
    fs::path file {"ultimate_test.lzma"};
    tson::Tileson t;
    std::unique_ptr<tson::Map> map = t.parse(file, std::make_unique<tson::Lzma>());

    return (map->getStatus() == tson::ParseStatus::OK) ? 0 : 1;
}

While there are no plans to support other types of compression, you can easily implement your own override of IDecompressor<std::vector<uint8_t>, std::vector<uint8_t>> and create support for your own compression. See include/common/Lzma.hpp for an example how this is implemented using PocketLzma.

Using an alternative Json parser

Tileson now uses a tson::IJson abstraction to make it possible to use several possible Json backends. It even makes it possible for the user to define his/her own IJson implementation using whatever Json library they like. Implementations for Json11 (default), Nlohmann and Picojson (there are single-header versions of these libraries in the extras folder).

Example:

#include "nlohmann.hpp" //Must be included before tileson.hpp
#include "picojson.hpp" //Must be included before tileson.hpp

#include "tileson.hpp"

int main(int argc, char **argv)
{
    tson::Tileson j11; //This will use the Json11-backend (default)
    tson::Tileson nlohmann {std::make_unique<tson::NlohmannJson>()}; //Uses the Nlohmann backend
    tson::Tileson picojson {std::make_unique<tson::PicoJson>()}; //Uses the Picojson backend

    tson::Project projectJ11; //This will use the Json11-backend (default)
    tson::Project projectNlohmann {std::make_unique<tson::NlohmannJson>()};
    tson::Project projectPicojson {std::make_unique<tson::PicoJson>()};

    tson::World worldJ11; //This will use the Json11-backend (default)
    tson::World worldNlohmann {std::make_unique<tson::NlohmannJson>()};
    tson::World worldPicojson {std::make_unique<tson::PicoJson>()};
    
    return 0;
}

You may notice that there is a gason.hpp file inside include/external and a Gason.hpp inside include/json. I tried to make this library work, and it passed the simple tests, but when reading a regular map I experienced memory corruption problems after a few layers was read, which would be hard to figure out. Gason is thus not supported, but is left there in case someone wants to experiment with it. Gason is blazingly fast, but also does a lot of memory trickery which I suspect is the reason why I had issues making it work.

Compiling

The program is cross-platform. It utilizes the features of modern C++ (C++17), which requires the user to have a pretty up to date compiler. Tileson specifically supports the compilers MSVC (Windows), GCC (Linux) and Clang (Mac/OSX), but other compilers supporting all C++17 features should be fine.

As a default, compiling examples are disabled due to CI, but it can easily be enabled through the CMakeLists.txt file on the root level of the project. Also keep in mind that the examples are using content from the tileson/content folder and are using relative paths. This is where the executable usually is located when compiling, and must have the same relative path to find the files:

alt text

Windows

For compiling the demo on Windows, it's recommended to use either Visual Studio or Visual Studio Code and open the CMakeLists.txt on the root level of the project. Examples for Windows required a x86-build in the past, but x64 should now work just as well! Some screenshots to showcase how CMake should be configured to build the demo using Visual Studio 2019:

Locate the CMakeLists.txt in the root of this project

alt text

Configure build to be x86 or x64

alt text

And then

alt text alt text alt text

Output after full build

alt text

Linux

If you have GCC7, GCC8, GCC9, GCC10 or newer as a compiler on your system, you should be good to go! If you are okay with the default settings: Just calling the cmake CMakeLists.txt inside the project folder will create a Makefile for you. Then you can compile the library by simply calling make.

OSX

std::filesystem should be supported by the Apple Clang-compiler shipped with the latest version of Mac OSX, but may not work with older versions of this OS. If you are using an old version of OSX you can, however, install the newest version of llvm via Homebrew, which has supported std::filesystem for a while. To generate a solution to be able to build this library, you will need to open the CMakeLists.txt file in CMake. If CMake is not installed on your system, it can easily be found on the internet.

Examples

You can find examples on how to draw the demo maps/projects using frameworks like SFML under examples. To build this, you will have to enable the CMake flag BUILD_EXAMPLES. All examples requires C++17 std::filesystem to work, and will fail to build on compilers not supporting this feature.

Generating the single-header

A single-header version of Tileson can be generated by calling the an amalgamate script in the tools/ folder. Depending on your system, you'd like to run one of the following scripts from a terminal or command prompt:

  • On Windows: amalgamate_script.bat
  • On Linux: amalgamate_script.sh
  • On Mac: amalgamate_osx.sh

Libraries used by Tileson

Optional Json parsers supported by Tileson

The json libraries supported can all be found in a single-header format inside the extras folder.

Libraries used in examples

  • SFML - For drawing maps.
  • Dear ImGui - For displaying information and managing maps.
  • ImGui-SFML - To render Dear ImGui with SFML.

tileson's People

Contributors

davidreynolds avatar dmlary avatar gamecoder-nz avatar jakehffn avatar jpeletier avatar laguna1989 avatar matthew-nagy avatar robloach avatar ssbmtonberry avatar tmpsantos avatar twje avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

tileson's Issues

IJson and IDecompressor have no virtual destructor

Despite being pure virtual classes, their destructors have not been explicitly marked as virtual. This can cause derrived classes to not have their destructors called correctly.
It also makes clang++ on linux throw me a warning a mile long, and adding these changes to my forked version fixed these issues. If you'd like, I can create a pull request.

Add support for fully LZMA compressed maps.

LZMA is a great compression, and I've had great results with it in the past! LZMA is also fully public domain and it should be easy for anyone to find something that can compress into lzma.

I've also tested it with Tiled .json files with extremely good results, so I think this would be a very useful addition to Tileson.
Best compression result is when Base64 is NOT used, and with the Minimize output export option on in Tiled.

My compression results:

Map name Before compression After compression Description
ultimate_test.json 67,0 KiB 2,3 KiB This uses regular CSV output
ultimate_test_base64.json 71,8 KiB 2,4 KiB This uses Base64 output
ultimate_test_min.json 21,0 KiB 1,8 KiB This was exported with the Minimize output option

u8_string problems

I'm trying to use this library in a game i'm working on but I get many errors like this.(I'm using clang 13 and c++20 on windows. I tried msys install and also the visual studio bundled compiler):

tileson_parser.hpp:110:12: error: no matching conversion for functional-style cast from 'std::filesystem::__u8_string' (aka 'basic_string<char8_t>') to 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char>>')
    msg += std::string(path.u8string());

C:/msys64/ucrt64/include/c++/v1/string:812:40: note: candidate constructor not viable: no known conversion from 'std::filesystem::__u8_string' (aka 'basic_string<char8_t>') to 'const std::basic_string<char>::allocator_type' (aka 'const std::allocator<char>') for 1st argument
    _LIBCPP_INLINE_VISIBILITY explicit basic_string(const allocator_type& __a)

I can't figure oue what the error could be.

Implement new data added after Tiled 1.2.4 (v1.2.4 to v1.4.0)

There have been a few changes to Tiled since the initial version of Tileson. Implement these. Up to version 1.4.0, which at this time is in Beta. Also verify that all the tags presented for v1.2.0 in Tiled's changelog already are implemented.

Changelog

Tiled 1.4

  • Added objectalignment to the Tileset object.
  • Added tintcolor to the Layer object.

Tiled 1.3

  • Added an editorsettings property to top-level Map and Tileset objects, which is used to store editor specific settings that are generally not relevant when loading a map or tileset.
  • Added support for Zstandard compression for tile layer data ("compression": "zstd" on tile layer objects).
    Added the compressionlevel property to the Map object, which stores the compression level to use for compressed tile layer data.

Tiled 1.2

  • Added nextlayerid to the Map object.
  • Added id to the Layer object.
  • The tiles in a Tileset are now stored as an array instead of an object. Previously the tile IDs were stored as string keys of the “tiles” object, now they are stored as id property of each Tile object.
  • Custom tile properties are now stored within each Tile instead of being included as tileproperties in the Tileset object.
  • Custom properties are now stored in an array instead of an object where the property names were the keys. Each property is now an object that stores the name, type and value of the property. The separate propertytypes and tilepropertytypes attributes have been removed.

Remove warnings

Hi, I am compiling tileson with -Wall -Wextra -Wpedantic and gcc version 9.3.0 and I am getting some warnings, I have fixed three of them that you can see in this file warnings.txt. If you agree I can create a pull request with the changes.

There are other warnings that involve std::move that I can also fix if you want, you can see them here all_warnings.txt. I didn't remove the std::move because maybe there is some reason for it that I am not aware of.

Thank you.

Add more detailed error handling.

Right now the error handing is very basic. It tells you whether your map failed during parsing or not, but nothing more.
There should be proper trace with a message as to what failed when things go wrong, so the user can figure out why it's failing.

Tileson::parse() doesn't parse, printing"expected value, got '<' (60)"

Hi! Im trying to use this library but i have a problem, any fs::path that I pass to method Tileson::parse() doesn't work, some of them cause printing "expected value, got '<' (60)" wich is hard to decrypt.
Case 1: json file is in the VS project folder
image
Console is empty
image
Tried also with these pathes:
".tileMap1sss.json"
"/tileMap1sss.json"
"./tileMap1sss.json"
Case 2: json file is in the folder "Map" that located in the VS project folder
image
Message printed
image
Tried also with these pathes:
".Map/tileMap1sss.json"
"/Map/tileMap1sss.json"
"./Map/tileMap1sss.json"

I will be really happy if you can help with it, not sure if its ok to create issue because of this. Also I'm sorry for my English I hope it's clear what my problem is.

Add support for "Collection of Images" tilsets

It looks like "Collection of Images" tilsets (described in the tiled docs here: https://doc.mapeditor.org/en/stable/manual/editing-tilesets/#two-types-of-tileset) aren't supported.

On Linux, trying to load a map that contains such a tileset results in a SIGFPE due to a division by zero error whilst trying to parse the tileset:

(gdb) run
<snip>
0x000055555568337c in tson::Tile::performDataCalculations (this=0x555555992120) at /home/gfg/git/foo/ext/tileson/include/common/tileson_forward.hpp:59
59          int rows = m_tileset->getTileCount() / columns;
(gdb) bt
#0  0x000055555568337c in tson::Tile::performDataCalculations (this=0x555555992120) at /home/gfg/git/foo/ext/tileson/include/common/tileson_forward.hpp:59
#1  0x000055555567ec8f in tson::Tile::parse (this=0x555555992120, json=..., tileset=0x555555ed59e0, map=0x555555ce2da0) at /home/gfg/git/foo/ext/tileson/include/tiled/Tile.hpp:175
#2  0x000055555567dba4 in tson::Tile::Tile (this=0x555555992120, json=..., tileset=0x555555ed59e0, map=0x555555ce2da0) at /home/gfg/git/foo/ext/tileson/include/tiled/Tile.hpp:102
#3  0x00005555556ab2c3 in __gnu_cxx::new_allocator<tson::Tile>::construct<tson::Tile, tson::IJson&, tson::Tileset*, tson::Map*&> (this=0x555555ed5a90, __p=0x555555992120) at /usr/include/c++/7/ext/new_allocator.h:136
<snip>
(gdb) print columns
$1 = 0

Is this something that you'd consider adding support or accepting patches for?

Support for external tilesets

Would it be possible to support external tilesets (i.e. non-embedded ones)? The library is incredible, far and away the best one out there, but if I make a change to an embedded tileset having to go and change that for all maps is rather annoying!

Remove the DISABLE_CPP17_FILESYSTEM functionality.

As of version 1.2.0 (WIP) you can use std::string as paths instead of std::filesystem (C++17) by doing a #define DISABLE_CPP17_FILESYSTEM. Now the C++17 feature should be supported by all standard compilers for Windows, Linux and OSX, and should be removed. Some features in v1.2.0 will also only be available if std::filesystem is NOT disabled, as they are way too time consuming to implement without the std::filesystem. Therefore, especially for consistency, C++17 filesystem should be the only option from version 1.3.0. I just deprecated the parse() using std::string in Tileson, and it will be included in the next merge with master.

Isometric maps?

I couldn't run the examples to test but does your library make it easy to parse and draw isometric maps?

I like how simple the API is and allows me to fetch object properties directly without having to parse it myself. That's much more sane compared to other tiled libraries out there. I'd like to use it in my isometric project.

Support Flipped Tiles

It would be good to have support for x/y flips, right now I'm unable to identify flipping for other than Wang tiles, which are I suppose different from common tiles.

What could be done to read the flip data from the json map file?

Make tson::Tile hold Tileset information

Having the possibility to hold information based on the tileset, would make certain things much easier when using the objects.
This issue requires:

  • A full tileset offset.
  • A path to the actual tileset image.
  • A pointer to the related tileset.

How the offset can be retrieved today:

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

    //pos = position in tile units
    for (auto& [pos, tile] : layer.getTileData()) //Loops through absolutely all existing tiles
    {

        //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)
        {
            //Get position in pixel units
            tson::Vector2f position = {(float) std::get<0>(pos) * m_map.getTileSize().x, (float) std::get<1>(pos) * m_map.getTileSize().y};

            int baseTilePosition = (tile->getId() - firstId);

            int tileModX = (baseTilePosition % columns);
            int currentRow = (baseTilePosition / columns);
            int offsetX = (tileModX != 0) ? ((tileModX) * m_map.getTileSize().x) : (0 * m_map.getTileSize().x);
            int offsetY =  (currentRow < rows-1) ? (currentRow * m_map.getTileSize().y) : ((rows-1) * m_map.getTileSize().y);

            //Set sprite data to draw the tile
            sf::Sprite *sprite = storeAndLoadImage(tileset->getImage().u8string(), {0,0});
            if(sprite != nullptr)
            {
                sprite->setTextureRect({offsetX, offsetY, m_map.getTileSize().x, m_map.getTileSize().y});
                sprite->setPosition({position.x, position.y});
                m_window.draw(*sprite);
            }
        }
}

You should be able to get a Tileset from the map by GID

Right now you can only get a specific tileset by knowing its name.
If you want to resolve the related tileset for a tson::Object using a tile, you'll have to resolve it yourself, which can be a painful process. You'd have to resolve it by going through all tilesets and find out whether the GID is in range. Like this:

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

    //With this, I know that it's related to the tileset above (though I only have one tileset)
    if (tileId >= firstId && tileId <= lastId)
...

Thus, Tileson should provide a more user friendly way to resolve this.

If you use tson::TileObjects that are related to tiles, this is not a problem.

Tile ObjectGroup's not set properly if one or more tiles have no properties or objects

In the tileset JSON file, only tiles with properties or objects are mentioned. So it may list the tiles [1, 2, 3, 7]. When it does this, tileson puts all of tile 7's properties on objects on tile 4 instead, as it was the 4th entry in the json file. An example:
{ "id":1, "objectgroup": { "draworder":"index", "name":"", "objects":[ { "height":32, "id":1, "name":"", "properties":[ { "name":"Collision", "type":"string", "value":"Wall,Opaque" }], "rotation":0, "type":"", "visible":true, "width":32, "x":0, "y":0 }], "opacity":1, "type":"objectgroup", "visible":true, "x":0, "y":0 } }, { "id":3, "properties":[ { "name":"Example", "type":"string", "value":"Help, where is tile 2?" }] }
When tileson reads in the tileset, it incorrectly put this "Example" property in tile 2's object group, and nothing in tile 3's. Were tile 3 to have a property, it would put that in tile 2's properties as well.
So far my solution has just been to add dummy properties to all my tiles, but its not ideal and was really hard to debug!

Tile-IDs in animations are inconsistent with the other Tileson IDs

Tileson uses an ID mapping making sure the IDs are aways consistent, while Tiled sometimes got a diff in the IDs of a tile definition and a tile used in a tileset. Somehow the tile IDs for animations have never been handled correctly, and is thus inconsistent with the other IDs, which shows when attempting to map one of these IDs to a tile of a tileset.

Add support for Tiled-projects

With Tiled 1.4.0 maps and settings can now be set in Tiled projects, using the "tiled-project"-extension. This project file already uses json as an internal format, so it should be okay to integrate with Tileson

Create TileObject to easily render tiles.

Ref #3 and also #1 as well.
I am not entirely satisfied with that you have to pass in a position in tile units into a tson::Tile to get a position in pixels.
Though, with the functionality implemented in #1 making functionality to get the positions of a tile should be easy. All I really should need is a TileObject to be able to store the position and a pointer to the related tson::Tile.

Tile properties are only loaded for first tileset

Hi,

It seems that tile properties are only parsed for the first tileset and ignored for all following tilesets. The data is correctly written by Tiled and present in the json.
However tileson does not seem to parse the property information correctly for the second tileset.

I will attach a minimal working example json in the next comment. Please find the screenshot of the debugging information of the map here:

tested on the current master, commit id a1ec661

image

Make a tson::Animation class to handle tile animations

Right now getAnimation only gives frames, which is correct if blindly following the Tiled spec, but I'd rather give a pointer to a animation class instead, which can do simple animation handling as well. To save potential space and overhead, it should be a unique_ptr.

This i much related to #32 and the suggestion of a demo of animated tiles

Docs: Embedding Tileset in Map

I have been unsuccessful parsing until i embedded the tilesets in the map json. It would help if that would have been mentioned in the docs. I only got the idea that this was the issue when searching through your test cases.
Keep up the good work!

Add support for Tiled 1.6

Honestly no major changes has been done to the .json format, but there is one small breaking change:
The version property is now written as a string (“1.6”) instead of a number (1.5).

Use a minimalistic json library instead of nlohmann's json lib.

Even though nlohmann::json in my opinion is the best, most modern and easy to use json library for C++, it also has a rather large code base which makes Tileson extremely large when merging all the code into a single-file. As a result we get a single-file header who is much larger than what a single file library should be. Therefore I want to rewrite the parsing using a much smaller and minimalistic library who still can do the job needed to be done.

Make sure Tileson has full C++20 support!

It seems like there are problems when using C++20 with Tileson on Windows. Make sure Tileson builds OK when using C++20 and C++17 on all major compilers. That is MSVC, GCC and Clang

Retrieving Color property correctly.

Hello,

I'm wondering how you retrieve the Color property and then access the RGB values. I'm trying to make a text from TILED parse the correct color data and create a sf::Text.

Thanks

Create a contribution guideline

There are a few contributors PR-ing things from time to time.
Due to that there should be a guideline on how I want contributions and issues to be handled, as most people are not able to read my mind.

Make an option to actually build Tileson as a library.

While this always will be a singleheader library, it doesn't cost much to make it possible to build Tileson
as a library. An own tileson.h (not .hpp) should be amalgamated for this, so there is still one header file needed.
Remember to add flag for being able to choose static/dynamic library build.

Update the demo to showcase all new features for Tileson v1.2.0

This is a task to make sure the demo showcases the usage of all the new features.
Especially .world and Tiled projects are important, but also other minor changes.

Some things are already added, like changes to tson::Tile and the addition/usage of tson::TileObject.

This is obviously the last task of v1.2.0. Make sure the documentation also is regenerated before release.

Create an new release

I just spend three hours finding out why my program was throwing thousands of memory leaks. Eventual, I discovered that the IJson and IDecompressor classes were missing a destructor. I wanted to create an issue and pull request to notify you about this issue, but then I saw someone else fixed the same problem 14 days ago. I hope you will make a new release, so that other people won't make the same mistake as I did!

Create an abstraction of the json parser component.

Create an abstraction/interface of the JsonParser component, making it possible for the user to use a custom json parser if they want. Start with making nlohmann/json work with it, then create a version for the minimalistic json library who will replace nlohmann/json

drawingRect does not take spacing and margin into account

The variable m_drawingRect is assigned by the function tson::Tile::performDataCalculations(). This function does not take the spacing and margin of the tileset into account when calculating the offsetX and offsetY, so the resulting rect does not represent the actual tile.
I have not tested it, but maybe you could replace it by something like:

int offsetX = m_tileset->getMargin() + tileModX * (m_map->getTileSize().x + m_tileset->getSpacing() + 2*m_tileset->getMargin())

and similar to Y

Fix problem related to reported issue: Tile is nullptr, but expected object

After doing some investigation in the code, it might seem like it happens to tiles that have absolutely no property in any way, which will make Tiled not export it at all.
The big map used for tests has collisions on every tile, which makes every tile have some kind of property. This is probably the reason why it has not been spotted before.

See issue reported by RacheProu here:
https://discourse.mapeditor.org/t/tileson-a-tiled-json-parser-for-modern-c/4062/7

std::string_view has not been declared

I ignore why i am getting this error when building, I properly included the header in my project but this line
inline static std::vector<uint8_t> Base64DecodedStringToBytes(std::string_view str);
is preventing me from building due to the use of string_view, I even tried to include the header of string_view as well but it isn't working

Fix cl compiler warnings

It seems that when compiling tileson with the visual studio compiler (cl version 19.29.30038.1) there are some compiler warnings. Those are not critical and most of them can be fixed by adding a cast. Please see the attached log file

cl_comiler_warnings.txt

None of them seem critical to me - except a user would use more Gids than uint32_t could contain.

'Unhandled exception: std::system error at memory location' after using 'tileObject.GetTile()->getTileset()'

Error

I am using tiled and tileson to load the map into SFML.
it works fine when I set the tileset directly using 'level->getTileset("[Tileset name]").
but when i try to get the parent tileset of a tile I get an error when I use that tileset.

Relevant code:

if (level->getStatus() == tson::ParseStatus::OK)
    {
        for (auto& layer : level->getLayers())
        {
            //At first I only had one tileset so I just used this one but now that I have more tileset I needed to change this.
            tson::Tileset* tileset = level->getTileset("TestTiles");

            if (layer.getType() == tson::LayerType::TileLayer)
            {
                for (auto& [pos, tileObject] : layer.getTileObjects())
                {
                    //In the readme I found this to set the respective tileset for each tile I don't get an error on this line but the next time I try to use the tileset variable.
                    tileset = tileObject.getTile()->getTileset();

                    sf::Sprite sprite;
                    tson::Rect drawingRect = tileObject.getDrawingRect();
                    tson::Vector2f position = tileObject.getPosition();

                     //Here I get the error "Unhandled exception at 0x761FA842 in [SolutionName]: Microsoft C++ exeption: std::system_error at memory location 0x00EFECB8.
                    //this works when remove the line "tileset = tileObject.getTile()->getTileset();" and only use the getTileset("TestTiles").
                    fs::path path = tileset->getImage().u8string();
                    if (fs::exists(path))
                    {
                        sf::Texture* tex = new sf::Texture();
                        tex->loadFromFile(path.u8string());
                        sprite.setTexture(*tex);
                    }

Having trouble with the parser

#include <iostream>
#include "tileson.hpp"

int main() {
	tson::Tileson parser;
	std::unique_ptr<tson::Map> map = parser.parse(fs::path("./files/map.json"));

	if (map->getStatus() == tson::ParseStatus::MissingData) {
		std::cerr << "Map data not loaded" << std::endl;
		return -1;
	}


	return 0;
}

I've a simple program like so, when I run it, the console shows
image
I've definitely checked the json is in the right directory, but here's the json file as well https://pastebin.com/pQ155GBg
I've been debugging for hours, but I can't seem to figure out the issue
Please help me with resolving the issue

Thanks

Nullptr error on getposition when parsing json

So when I'm parsing my map JSON data(that only consists of one block). It will give me an error on GePosition()

image

So far what I have tried is getting tiled 1.2
switching to a single header
put it in a different folder
changing the tiled folder settings

this is my relevant code

#include "tileson.hpp"

	tson::Tileson t;
	std::unique_ptr<tson::Map> map = t.parse(fs::path("../SFML_RogueLike/Art/World/Test.json")); // <== this is where I get the nullptr error

	if (map->getStatus() == tson::ParseStatus::OK)
	{
		for (auto& layer : map->getLayers())
		{
			for (auto& obj : layer.getObjects())
			{
				sf::Vector2f groundPos = sf::Vector2f(obj.getPosition().x, obj.getPosition().y);
				sf::Vector2f groundSize = sf::Vector2f(obj.getSize().x, obj.getSize().y);

				platforms.push_back(Platform(&groundtexture, groundSize, groundPos));
			}
		}
	}
	else
	{
		std::cout << map->getStatusMessage() << std::endl;
	}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.