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

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Marz

Pages: [1]
1
SFML projects / 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!

Pages: [1]