Multidimensional Arrays in C++ for a tilemap in SFML
Monday, May 30, 2011 7:15:40 AM
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

