Gorilla3D

Blogs of my work and thoughts

Multidimensional Arrays in C++ for a tilemap in SFML

To understand this tutorial to must first know the basics of vectors in c++ and how to use SFML.

The Basics - Defining the Map

First lets invasion a 4x4 tile map. Each box will hold the tile ID, which is basically saying, "This box is grass", or "This box is water".

 _______
|_|_|_|_|
|_|_|_|_|
|_|_|_|_|
|_|_|_|_|


Now lets see how this works in C++. In C++ you can dynamically initialize the entire multidimensional array. However you cannot initialize each row dynamically unless you enable your compiler's support for c++0x. Not all compiler support "extended initializer lists".

int map[4][4] = {
    {0, 0, 0, 0}, // <-- There are 4 columns per row, and 4 rows
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}
}; // This works

int map[4][4];
map[0] = {0, 0, 0, 0}; // Fails unless you use enable c++0x support in your C++ compiler


You can also used C++'s standard vector library. I feel the use of vectors are a bit more easier to deal with when you want to access your map indexes. C++ vectors give you better tools to know the limits of your map before accessing an index of the map. Hence less likely to have your application crash. To used multidimentional vectors, you have to use a very type heavy syntax.

// much like "int map[4][4];"
std::vector< std::vector<int> > map(4, std::vector<int>(4));
map[0][0] = 0;
map[0][1] = 0;
// ... 

// If you want to use the same initialization as the previous example 
// you will have to enable c++0x  

// !!! NOTE !!!
// Using the format below will fail because >> in C++ means something
// different
// std::vector<std::vector<int>> map(4, std::vector<int>(4));


How to Access Multidimensional Indexes - Ermm Map Tiles

So lets stick with vectors for now. They are a bit more complicate to initialize but doing this forces you to understand how arrays look in a programming language.

Remember our map?
 _______
|_|_|_|_|
|_|_|_|_|
|_|_|_|_|
|_|_|_|_|


Can you tell me where 2,3 is? Well look at the map, down 2, left 3.
 _______
|_|_|_|_|
|_|_|X|_|
|_|_|_|_|
|_|_|_|_|


Well how do you access that tile (2,3) in C++? Even more so while using vectors?
int tileId = map[1][2]; // This is not a typo it really is [1][2], read below!
// Or
int tileId = map.at(1).at(2);


Well that makes since right? Not really, this is because zero (0) has a size in C++. So zero (0) is really the first index, not one (1).

How to Iterate aka "Array Walk" your map

Because we are using vectors you'd be tempted to use the vector iterator, but because we are using a complex vector array, its much better off to not use them. So instead we will use a regular for loop for the X and Y based tiles in the map. Since the map uses vectors we can get the size of each vector dynamically.

    std::vector< std::vector<int> > map(4, std::vector<int>(4));
    // Define the map
    map[0][0] = 0;
    map[0][1] = 1;
    map[0][2] = 0;
    map[0][3] = 0;
    map[1][0] = 0;
    map[1][1] = 0;
    map[1][2] = 0;
    map[1][3] = 0;
    map[2][0] = 0;
    map[2][1] = 0;
    map[2][2] = 0;
    map[2][3] = 0;
    map[3][0] = 0;
    map[3][1] = 0;
    map[3][2] = 0;
    map[3][3] = 1;
    
    
    for (int x = 0; x < map.size(); x++) {
        for (int y = 0; y < map[x].size(); y++) {
            int tileId = map[x][y];
            std::cout << tileId << "\n";
        }
    }



Slapping SFML into the mix

Before we start I will have you use a startup file to have SFML setup with out basic map and a grass / water tile to be loaded.

Tutorial-01.zip

First lets load our images and setup sprites for them. Those zeros and ones will now have meaning to them. At these lines just below line 29 sf::RenderWindow App(sf::VideoMode(640, 480, 32), "SFML TileMap");
    sf::RenderWindow App(sf::VideoMode(640, 480, 32), "SFML TileMap");

    // Load the images from files
    std::vector<sf::Image> images(2); // Preset the vector to 2 images
    if (!images[0].LoadFromFile("grass.png")) {
        return EXIT_FAILURE;
    }
    if (!images[1].LoadFromFile("water.png")) {
        return EXIT_FAILURE;
    }

    // Create the sprite
    std::vector<sf::Sprite> tiles(2); // Preset the vector to 2 sprites
    tiles[0] = sf::Sprite(images[0]); // Grass
    tiles[1] = sf::Sprite(images[1]); // Water


Now if you notice tiles[0] and tiles[1], the 0 and 1 are what we used in the map! Next we take our map walk over it and associate it to these sprites. Locate App.Clear, just below that we have our array walk happening. Go ahead and compile the code below and see the what happens.

        for (int x = 0; x < map.size (); x++) {
            for (int y = 0; y < map[x].size (); y++) {
                int tileId = map[x][y];
                App.Draw(tiles[tileId]); // Add this like ... WE DRAW TILES YAY
                std::cout << tileId << "\n";
            }
        }




Uhh wait they are just showing up as one water tile, what gives? Well its because the position each tile needs to be set before drawing. So lets give it a try. Assign the x and y coordinates from the map as the position of the tile. Now compile again and see the result.

        for (int x = 0; x < map.size (); x++) {
            for (int y = 0; y < map[x].size (); y++) {
                int tileId = map[x][y];
                
                tiles[tileId].SetPosition(x, y);
                App.Draw(tiles[tileId]);
                std::cout << tileId << "\n";
            }
        }




Ok well you see some grass? I think... Well thats for good reason, we only push the tile over 1 pixel. Since the x and y variables are represented as 1 pixel, we will have to use the width and height of each sprite's image to get the right result.

So lets go back to or example of find out the coordinate (2,3).

 _______
|_|_|_|_|
|_|_|X|_|
|_|_|_|_|
|_|_|_|_|


To find the number of pixels we need to push the tile over to draw in the right spot we need a constant width and height. So if each tile is 32 pixels wide and 32 pixels tall, then when you get to the 2nd tile down whats your starting position? Think for a second, the 2nd tile was really "1" in the map index. map[1] = 2nd tile down, with that you know that its going to start at 32 pixels, not 64 pixels, on the x-axis. What about the y-axis? Well again that 3rd tile was really "2". So again map[1][2] = (2,3). So this time is 2 tiles down, 32 + 32 pixels = 64 pixel. So x = 32, y = 64... Now put on your thinking cap, if we start at zero, want to render the 3rd tile and its really 2 tiles over, then you get the formula y_pixels = 32 * y (the maps y in the for loop).

        for (int x = 0; x < map.size (); x++) {
            for (int y = 0; y < map[x].size (); y++) {
                int tileId = map[x][y];
                // Get the tile's image
                const sf::Image* image = tiles[tileId].GetImage();
                // Get the width and height of the image
                int width = image->GetWidth();
                int height = image->GetHeight();
                // Adjust the offset by using the width
                tiles[tileId].SetPosition(x * width, y * height);
                // Draw the tile
                App.Draw(tiles[tileId]);
            }
        }


So go ahead and compile the example see what you get!



If all else fails, then I have the entire tutorial source below:
sfml-tilemap2.zip

You can also view the source online:
https://gist.github.com/998540

Php on Windows is ...How the language Vala can speed up development time frames.