Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Tiled (mapeditor.org) Drawable Class  (Read 2335 times)

0 Members and 1 Guest are viewing this topic.

Marz

  • Newbie
  • *
  • Posts: 1
    • View Profile
    • Email
Tiled (mapeditor.org) Drawable Class
« on: June 30, 2013, 05:23:32 am »
Finally took the plunge into SFML today, and managed to create a class that may be of use to others. It takes a Tiled .tmx file and gives you a Drawable class that you can just window.draw() onto the screen. I saw there was another forum post with something similar, but this one uses the VertexArray tutorial I read (I believe the other forum post drew everything as individual sprites).

DrawableMap.h
#pragma once

#include <SFML/Graphics.hpp>
#include "../TmxParser/Tmx.h"

#include <vector>

class DrawableMap : public sf::Drawable, public sf::Transformable {
public:
        DrawableMap();

        /**
         * Load a map with the given filename from the specified path.
         */

        bool Load(const std::string& path, const std::string& filename);
private:
        /**
         * Single-texture drawable layer
         */

        class DrawableLayer : public sf::Drawable, public sf::Transformable {
        public:
                bool Load(const Tmx::Layer* layer, const Tmx::Tileset *tileset, const std::string& path);
        private:
                virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;

                bool inTileset(int tile_id, const Tmx::Tileset *tileset);

                sf::VertexArray vertices_;
                sf::Texture texture_;
        };

        virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;

        Tmx::Map* map_;
        std::vector<DrawableLayer*> layers;
};
 

DrawableMap.cpp
#include "DrawableMap.h"

bool DrawableMap::DrawableLayer::Load(const Tmx::Layer* layer, const Tmx::Tileset *tileset, const std::string& path) {
        //Ensure non-null
        if (layer == nullptr || tileset == nullptr) {
                return false;
        }

        //Load tileset texture
        if(!texture_.loadFromFile(path + tileset->GetImage()->GetSource())) {
                return false;
        }

        vertices_.setPrimitiveType(sf::Quads);
        vertices_.resize(layer->GetWidth() * layer->GetHeight() * 4);

        for (int y = 0; y < layer->GetHeight(); ++y) {
                for (int x = 0; x < layer->GetWidth(); ++x) {
                        //Get the tile ID
                        unsigned int tile_id = layer->GetTileId(x, y);
                        if(!inTileset(tile_id, tileset)) {
                                //Tile does not belong to our tileset, do not draw
                                continue;
                        }
                       
                        int tileset_index = layer->GetTileTilesetIndex(x, y);

                        int tile_width = tileset->GetTileWidth();
                        int tile_height = tileset->GetTileHeight();
                        int tile_spacing = tileset->GetSpacing();
                        int tile_margin = tileset->GetMargin();

                        //Calculate tileset coordinates
                        tile_id -= tileset->GetFirstGid();
                        int tu = tile_id % ((tileset->GetImage()->GetWidth() - tile_margin)
                                / (tile_width + tile_spacing));
                        int tv = tile_id / ((tileset->GetImage()->GetWidth() - tile_margin)
                                / (tile_width + tile_spacing));

                        //Setup the tile
                        sf::Vertex* quad = &vertices_[(y * layer->GetWidth() + x) * 4];

                        //define its 4 corners
                        quad[0].position = sf::Vector2f(x * tile_width, y * tile_height);
                        quad[1].position = sf::Vector2f((x + 1) * tile_width, y * tile_height);
                        quad[2].position = sf::Vector2f((x + 1) * tile_width, (y + 1) * tile_height);
                        quad[3].position = sf::Vector2f(x * tile_width, (y + 1) * tile_height);

                        //define its 4 texture coordinates
                        quad[0].texCoords = sf::Vector2f(tu * (tile_width + tile_spacing) + tile_margin,
                                tv * (tile_height + tile_spacing) + tile_margin);
                        quad[1].texCoords = sf::Vector2f((tu + 1) * (tile_width + tile_spacing) + tile_margin
                                - tile_spacing, tv * (tile_height + tile_spacing) + tile_margin);
                        quad[2].texCoords = sf::Vector2f((tu + 1) * (tile_width + tile_spacing) + tile_margin
                                - tile_spacing, (tv + 1) * (tile_height + tile_spacing) + tile_margin - tile_spacing);
                        quad[3].texCoords = sf::Vector2f(tu * (tile_width + tile_spacing) + tile_margin,
                                (tv + 1) * (tile_height + tile_spacing) + tile_margin - tile_spacing);
                       
                }
        }
}

bool DrawableMap::DrawableLayer::inTileset(int tile_id, const Tmx::Tileset *tileset) {
        int tiles_x = (tileset->GetImage()->GetWidth() - tileset->GetMargin()) / (tileset->GetTileWidth() + tileset->GetSpacing());
        int tiles_y = (tileset->GetImage()->GetHeight() - tileset->GetMargin()) / (tileset->GetTileHeight() + tileset->GetSpacing());
        int total_tiles = tiles_x * tiles_y;

        if((tile_id >= tileset->GetFirstGid()) && (tile_id <= tileset->GetFirstGid() + total_tiles)) {
                return true;
        }

        return false;
}

void DrawableMap::DrawableLayer::draw(sf::RenderTarget& target, sf::RenderStates states) const {
        // apply the transform
        states.transform *= getTransform();
        // apply the tileset texture
        states.texture = &texture_;
        // draw the vertex array
        target.draw(vertices_, states);

}

DrawableMap::DrawableMap() : map_(nullptr) {
}

bool DrawableMap::Load(const std::string& path, const std::string& filename) {
        //Load and parse the TMX Map
        map_ = new Tmx::Map();
        map_->ParseFile(path + filename);

        if(map_->HasError()) {
                fprintf(stderr, "[Error]::Map(%d) %s\n", map_->GetErrorCode(), map_->GetErrorText().c_str());

                return false;
        }

        //Break up the map by layers
        for (int l = 0; l < map_->GetNumLayers(); ++l) {
                const Tmx::Layer *layer = map_->GetLayer(l);

                //Break up the layers by tilesets
                for(int t = 0; t < map_->GetNumTilesets(); ++t) {
                        DrawableLayer* d_layer = new DrawableLayer();

                        // TODO: Check for error?
                        d_layer->Load(layer, map_->GetTileset(t), path);
                        layers.push_back(d_layer);
                }
        }      

        return true;
}

void DrawableMap::draw(sf::RenderTarget& target, sf::RenderStates states) const {
        for(unsigned int i = 0; i < layers.size(); ++i) {
                target.draw(*layers[i], states);
        }
}
 

You will need to use tmx-parser (https://code.google.com/p/tmx-parser/) to load the file. I'm ok with reinventing the wheel for fun stuff like drawing, but not parsing XML files :P.

It could still use some optimizations, such as a texture manager to prevent loading the same tileset more than once for each layer. But I was happy I could do this quickly, so thanks for SFML Laurent!