Slava Zoref
Slava Zoref

Reputation: 315

drawing a tile map with sfml

So, I've been trying to create a 'Map' class to handle the drawing of my tile maps on the window but I'm having a problem to draw all the tiles to the screen.

This is my problem: lets say i have a map of 10*10 tiles with 5 layers which sums up to 10*10*5 = 500 tiles ( of course in real time there will be more likely 10 times more tiles which would be worse )... and it is stored in a 3d array [layer][row][col] or chars, each char representing an index on my sprite sheet of tiles so i created a 'SpriteSheet' class to handle my sprite sheets and get each sprite from the sheet by an index: spriteSheet->GetSubSprite(idx). so in the end i have something like Draw(spriteSheet->GetSubSprite((int)mapArray[i][j][k])) which should be called for each sprite, EVERY GAME LOOP so lets say my game runs on 60 fps and i have 500 tiles to draw: the game aims to draw 500*60 = 30,000(!) tiles each second...

so as you can see im having abit of a problem here...? anybody knows how to sufficiently in terms of speed imporve this? ( and of course any kind of improvement to my current structure would be very blessed ).

So, just in case here is my SpriteSheet.cpp and my Map.cpp files, I know I have many design mistakes but please focus on my question, the design is far from finished.


#include "Map.h"

sz::Map::Map(std::string path, std::string name, std::string image) : Tmx::Map() {
    ParseFile(path);

    if (HasError()) {
        printf("error code: %d\n", GetErrorCode());
        printf("error text: %s\n", GetErrorText().c_str());

        system("PAUSE");
    } 
    else {
        mapArray = new unsigned char**[GetNumLayers()];
        for(int i = 0; i < GetNumLayers(); i++) {
            mapArray[i] = new unsigned char*[GetLayer(i)->GetHeight()];
            for(int j=0; j<GetLayer(i)->GetHeight(); j++) {
                mapArray[i][j] = new unsigned char[GetLayer(i)->GetWidth()];
                // [layer][row][col]
            }
        }
        //load the array
        for (int i = 0; i < GetNumLayers(); ++i) {

            // Get a layer.
            const Tmx::Layer *layer = GetLayer(i);

            for (int y = 0; y < layer->GetHeight(); ++y) {
                for (int x = 0; x < layer->GetWidth(); ++x) {
                    // Get a tile global id.
                    mapArray[i][x][y] = (char)layer->GetTileGid(x, y);
                    //printf("%3d", mapArray[i][x][y]);
                    /* need to fix later on
                    /****************************
                    // Find a tileset for that id.
                    const Tmx::Tileset *tileset = FindTileset(layer->GetTileGid(x, y));
                    if (layer->IsTileFlippedHorizontally(x, y)){
                        printf("h");
                    }else{
                        printf(" ");
                    }
                    if (layer->IsTileFlippedVertically(x, y)){
                        printf("v");
                    }else{
                        printf(" ");
                    }
                    if (layer->IsTileFlippedDiagonally(x, y)){
                        printf("d ");
                    } else {
                        printf("  ");
                    }
                    ****************************/
                }
                //printf("\n");
            }
            //printf("\n\n");
        }
    }
    tiles = new sz::SpriteSheet(name, image, 33, 33);
}

void sz::Map::Draw(sf::RenderWindow *rw) {
    // dont know what to do T_T
}

#include "SpriteSheet.h"

sz::SpriteSheet::SpriteSheet(std::string name, std::string path, int tileW, int tileH) : sz::GameObject(name, path) {
    this->tileH = tileH;
    this->tileW = tileW;
    numOfTiles = ((this->GetImage()->GetHeight()) / tileH) * ((this->GetImage()->GetWidth()) / tileW);
}

int sz::SpriteSheet::GetTileWidth() { return tileW; }
int sz::SpriteSheet::GetTileHeight() { return tileH; }
int sz::SpriteSheet::GetNumOfTiles() { return numOfTiles; }

sf::Sprite sz::SpriteSheet::GetSubSprite(int idx) {
    if(idx < 1 || idx > numOfTiles) {
        std::cout << "Incorrect index!" << std::endl;
        // need return value
    }

    int row=0, col=0, tilesEachRow = (GetImage()->GetWidth() / tileW);

    while(idx > tilesEachRow) {
        idx -= tilesEachRow;
        col++;
    }
    row = idx-1;

    sz::GameObject *sub = new sz::GameObject(name, path);
    /*std::cout << "tileW: " << tileW << std::endl;
    std::cout << "tileH: " << tileH << std::endl;
    std::cout << "row: " << row << std::endl;
    std::cout << "col: " << col << std::endl;
    std::cout << "tiles per row: " << tilesEachRow << std::endl;
    std::cout << "(" << row*tileW << ", " << col*tileH << ", " << (row+1)*tileW << ", " << (col+1)*tileH << ")" << std::endl;*/
    sub->SetSubRect(sf::IntRect(row*tileW, col*tileH, (row+1)*tileW, (col+1)*tileH));
    return *sub;
}

Upvotes: 1

Views: 10986

Answers (2)

priomsrb
priomsrb

Reputation: 2652

I agree with Alex Z in that with modern/mainstream hardware rendering 5 screens worth of tiles should not be too slow. However the only way to know for sure is to test it.

Also you need to realize that even if your map has 1000x1000 tiles, you will only be rendering 10x10 worth of tiles per frame because the rest of the tiles are outside the screen.

If you want to make your current structure more optimized, you should store each of the sprites in your sprite sheet as seperate sprites in an array. That way you will not need to call SetSubRect everytime you need to access a single tile.

Upvotes: 1

Alex Z
Alex Z

Reputation: 2590

Are you actually experiencing any speed issues? While 30,000 tiles per second may sound like a lot, in terms of computing with a more modern PC, it may not be much of a problem.

Having said this, I can definitely think of a way to optimise what you have if outputting a single tile has a certain overhead associated with it (eg. 2 tiles takes longer to output than 1 tile that is the size of both tiles combined). I don't profess to know much in terms of SFML, but I assume there is some sort of surfaces/canvas system in place. What you could do is write a method that draws static tiles to a surface, once. Then output this surface once each frame of the game. This means there is no need to repetitively output tiles every frame in a tile by tile fashion. You just output a single image of all the static tiles combined.

I reckon you could even use this system dynamically. If a tile needs to change, simply overwrite the relevant part of this surface with the updated tile data. In other words, you leave everything that is static alone and only target areas that require updating.

Upvotes: 4

Related Questions