Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411510 Posts in 69375 Topics- by 58430 Members - Latest Member: Jesse Webb

April 26, 2024, 12:01:51 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallForum IssuesArchived subforums (read only)TutorialsImplementing a tile engine, Tiled
Pages: [1]
Print
Author Topic: Implementing a tile engine, Tiled  (Read 1943 times)
soryy708
Level 3
***


View Profile WWW
« on: October 31, 2013, 10:47:10 PM »

Hi.
For those unfamiliar with the program, today we will implement support for Tiled into our C++ program.
We will use TmxParser for this task.

The GameObject
We will use a component based design just for the sake of it, and to make the people here happy. This is some boilerplate that you need:
Code:
namespace GameObject
{
    class AComponent
    {
    public:
        typedef std::vector<AComponent*> ComponentContainer;

    private:
        static ComponentContainer all;

    public:
        static ComponentContainer getAll() { return all; }

        virtual void update() {}

        AComponent()
        {
            all.push_back(this);
        }
        ~AComponent()
        {
            auto i = std::find(all.begin(), all.end(), this);
            if (i != all.end())
            {
                all.erase(i);
            }
        }
    };
    AComponent::ComponentContainer AComponent::all;

    class PositionComponent : public AComponent
    {
    private:
        int x;
        int y;

    public:
        int getX() const { return x; }
        int getY() const { return y; }
        void setX(const int v) { x = v; }
        void setY(const int v) { y = v; }

        PositionComponent() :
                x(0),
                y(0)
        {
        }
        PositionComponent(const int x, const int y) :
                x(x),
                y(y)
        {
        }
    };

    class SizeComponent : public AComponent
    {
    private:
        int w;
        int h;

    public:
        int getW() const { return w; }
        int getH() const { return h; }
        void setW(const int v) { w = v; }
        void setH(const int v) { h = v; }

        SizeComponent() :
                w(0),
                h(0)
        {
        }
        SizeComponent(const int w, const int h) :
                w(w),
                h(h)
        {
        }
    };
}
And for the graphics:
Code:
namespace GameObject
{
    class GraphicComponent
    {
    public:
        typedef std::vector<GraphicComponent*> GraphicComponentContainer;

    private:
        static GraphicComponentContainer all;
        int sprite_number;
        Video::SpriteSheet& spritesheet;
        PositionComponent& position;
        SizeComponent& size;

    public:
        static GraphicComponentContainer getAll() { return all; }

        int getSpriteNumber() const { return sprite_number; }
        Video::SpriteSheet& getSpritesheet() const { return spritesheet; }
        PositionComponent getPosition() const { return position; }
        SizeComponent getSize() const { return size; }
        void setSpriteNumber(const int v) { sprite_number = v; }

        GraphicComponent(Video::SpriteSheet& spritesheet, PositionComponent& position, SizeComponent& size) :
                sprite_number(0),
                spritesheet(spritesheet),
                position(position),
                size(size)
        {
            all.push_back(this);
        }
        ~GraphicComponent()
        {
            auto i = std::find(all.begin(), all.end(), this);
            if (i != all.end())
            {
                all.erase(i);
            }
        }
    };
    GraphicComponent::GraphicComponentContainer GraphicComponent::all;
}
So now just use composition to do all sorts of GameObjects.

The Tile GameObject
As I said, use composition to do our GameObject.
Code:
class Tile
{
private:
    GameObject::PositionComponent position;
    GameObject::SizeComponent size;
    GameObject::GraphicComponent graphic;

public:
    Tile(Video::SpriteSheet& spritesheet, GameObject::PositionComponent position, GameObject::SizeComponent size) :
            position(position),
            size(size),
            graphic(GameObject::GraphicComponent(spritesheet, position, size))
    {
    }
};

The tile map itself
Code:
class TileSet
{
private:
    Video::SpriteSheet* spritesheet;
    unsigned int first_gid;

public:
    Video::SpriteSheet& getSpriteSheet() const { return *spritesheet; }
    unsigned int getFirstGid() const { return first_gid; }

    TileSet(SDL_Renderer* renderer, Tmx::Tileset* tileset, const char* map_path)
        std::string path = map_path + tileset->GetImage()->GetSource();
        SDL_Surface* sdlsurf = IMG_Load(path.c_str());
        if (sdlsurf == nullptr)
        {
            throw(std::exception(IMG_GetError()));
        }
    
        spritesheet = new Video::SpriteSheet(renderer, sdlsurf, tileset->GetTileWidth(), tileset->GetTileHeight());

        SDL_FreeSurface(sdlsurf);
    }
    
    ~TileSet()
    {
        delete spritesheet;
    }
};

class TileMap
{
public:
    typedef std::vector<TileSet*> TileSetsContainer;

private:
    TileSetsContainer tilesets;
    std::vector<Tile*> created_tiles;
    int tw;
    int th;

public:
    int getTileWidth() const { return tw; }
    int getTileHeight() const { return th; }
    
    TileMap(SDL_Renderer* renderer, const char* path)
    {
        Tmx::Map map;

        map.ParseFile(path);

        tw = map.GetTileWidth();
        th = map.GetTileHeight();

        auto tmx_tilesets = map.GetTilesets();
        for (unsigned int i = 0; i < tmx_tilesets.size(); ++i)
        {
            tilesets.push_back(new TileSet(renderer, tmx_tilesets[i], map.GetFilepath().c_str()));
        }
    
        for (int layer = 0; layer < map.GetNumLayers(); ++layer)
        {
            for (int y = 0; y < map.GetLayer(layer)->GetHeight(); ++y)
            {
                for (int x = 0; x < map.GetLayer(layer)->GetWidth(); ++x)
                {
                    Tmx::MapTile tile = map.GetLayer(layer)->GetTile(x, y);

                    if (tile.tilesetId < tilesets.size() && tile.id >= tilesets[tile.tilesetId]->getFirstGid())
                    {
                        GameObject::PositionComponent position(map.GetTileWidth() * x, map.GetTileHeight() * y);
                        GameObject::SizeComponent size(map.GetTileWidth(), map.GetTileHeight());
                    
                        Tile* created_tile = new Tile(tilesets[tile.tilesetId]->getSpriteSheet(), position, size);

                        created_tiles.push_back(created_tile);
                    }
                }
            }
        }
    }

    ~TileMap()
    {
        for (auto i : created_tiles)
        {
            delete i;
        }
        created_tiles.clear();
   
        for (auto i : tilesets)
        {
            delete i;
        }
        tilesets.clear();
    }
};
« Last Edit: November 01, 2013, 09:59:09 PM by soryy708 » Logged
Gregg Williams
Level 10
*****


Retromite code daemon


View Profile WWW
« Reply #1 on: October 31, 2013, 11:27:27 PM »

Thanks for sharing. It seems like an interesting approach, though also like it becomes a bit heavy for something as simple as tile data.
Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #2 on: November 01, 2013, 09:42:33 PM »

Code:
    ~TileMap()
    {
        created_tiles.clear();
        tilesets.clear();
    }
};

This class is leaking memory.  Clearing those containers (which is entirely unnecessary, by the way) doesn't delete the objects that the pointers in the containers refer to.

Since you're using C++11, consider using std::unique_ptr in those containers.  Alternatively, you might be able to just store the objects by value instead dynamically allocating them.  You also really ought to implement a copy constructor/assignment or supress those operations to prevent further problems.
Logged



What would John Carmack do?
soryy708
Level 3
***


View Profile WWW
« Reply #3 on: November 01, 2013, 09:48:41 PM »

Quote
Thanks for sharing. It seems like an interesting approach, though also like it becomes a bit heavy for something as simple as tile data.
You're welcome.
I found that it becomes very easy to do collision detection if your tiles are not different from your game objects (that take part in collision detection).

Quote
This class is leaking memory.  Clearing those containers (which is entirely unnecessary, by the way) doesn't delete the objects that the pointers in the containers refer to.

Since you're using C++11, consider using std::unique_ptr in those containers.  Alternatively, you might be able to just store the objects by value instead dynamically allocating them.  You also really ought to implement a copy constructor/assignment or supress those operations to prevent further problems.
Thanks for pointing that out! I've noticed that I'm leaking memory but weren't sure where from. I love the benefits of FOSS.

I've updated the op.
« Last Edit: November 01, 2013, 10:01:07 PM by soryy708 » Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic