As I stated in the last lesson, we're going to be looking at making a Map class that will be tile based. In addition to the maps, we'll be creating Areas that enhouse many Maps. While we could create one giant map, it's far easier to manage many smaller maps, and also opens the possibility of tiling maps as well. By the way, head on over to the SDL Image tutorial if you haven't already done so, we will be making the switch over to SDL Image and stop using SDL_LoadBMP. No more chatter, lets get started.

Known Bug: The camera settings for this tutorial are backwards, and are reversed in the SDL Collision tutorial (and following). Changes are not put in place yet, to get the fixes view the SDL Collision tutorial.

We're going to need several files in the beginning, Define.h, CArea.h, CArea.cpp, CMap.h, CMap.cpp, CTile.h, and CTile.cpp. So create these blank files. What we are going to do is have one Area object, this Area object will have many children Map objects in a vector (like the Entities in a vector). Within each of these Map objects, they will have a vector of each individual tile. So basically, we have a bunch of tiles that make up a map, and a bunch of maps make up an area.

Lets start by opening up Define.h. This file is going to hold some constant values, as defines, that will define things like map width and height, screen width and height, and tile size. These are going to be some values that we will be using throughout making our Maps and such. Add the following below to define.h.
#ifndef _DEFINE_H_
    #define _DEFINE_H_

#define MAP_WIDTH    40
#define MAP_HEIGHT    40

#define TILE_SIZE    16

#define WWIDTH        640
#define    WHEIGHT        480

#endif
We'll get back to what these values mean later, but WWIDTH and WHEIGHT are the values set in SDL_SetVideoMode. So open up CApp_Init and change that function call to the following:
if((Surf_Display = SDL_SetVideoMode(WWIDTH, WHEIGHT, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
    return false;
}
Simple enough. Next, open up CTile.h. This class is going to define a single tile on a map. In gaming terms, a tile is basically a single square graphic that we draw to the screen. Remember the animation tutorial, each frame of Yoshi could be thought of as a tile. So when I say we have a map made up of many tiles, we are taking these tiles and repeating them in a grid sequence.

And since we have a single graphic containg all of our tiles, we only have to load one graphic, and not several for each tile. Each tile then needs some properties. The most obvious is which graphical tile to use. Take a look at an example tileset below:

You'll notice that the very top left has an ID of 0, and going to the left it increments in 1s. This is called the Tile ID. By using the ID, and the size of each tile (TILE_SIZE), we can know which tile to draw to the screen. The next important thing is TypeID. This determines what type of tile it is. Some examples might be, invisible (don't draw a tile), block (like a wall), or ground (player can walk on it). You can define whatever TypeIDs you want, but these are the most basic (other examples might be water, fire, ice, etc...). That's basically it, this Tile class only has two members (for now, later on it will contain information for animated tiles). So lets push this class together inside of CTile.h:
#ifndef _CTILE_H_
    #define _CTILE_H_

#include "Define.h"

enum {
    TILE_TYPE_NONE = 0,

    TILE_TYPE_NORMAL,
    TILE_TYPE_BLOCK
};

class CTile {
    public:
        int     TileID;
        int     TypeID;

    public:
        CTile();
};

#endif
Notice the enum used. This lets us assign or check the TypeID, and know what it is at the same time. So if I did TypeID == TILE_TYPE_BLOCK, I would be able to notice in my code that I am checking for a block type of tile. TypeID == 2 isn't as easy to interpret. If you never used enums, think of them like const variables. The value never changes. Also, the starting value defines the rest, notice I have = 0 on TILE_TYPE_NONE. From there the rest of the variables would automatically be assigned values in increments of 1.

Now, lets open up CTile.cpp and finish the code:
#include "CTile.h"

CTile::CTile() {
    TileID = 0;
    TypeID = TILE_TYPE_NONE;
}
Pretty straight forward. Next, lets look at making some maps. The first thing we need to consider is that our maps will be text files. So we need to come up with a file format, that way we can easily edit our maps outside of our code. And possibly later you could create a map editor.

Each map is going to have a width and height, defining the number of tiles. So a map of 10x10, would have 100 tiles in it. We are going to make all of our maps the same width and height, so we don't have to define that within the map files (MAP_WIDTH and MAP_HEIGHT). The part that we do need to add though is the TileID and TypeID for each individual tile. We are going to use the file format that I came up with, as it is relatively simple. Take a look at the 5x5 map example below:

0:0 0:0 0:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0
1:0 1:0 1:0 0:0 0:0

Each tile within the file would consist of 0:0, effectively being the TileID:TypeID. A space would be the deliminator between the tiles. A concept rendering if this map may look like this:

For this tutorial we will be using 40x40 maps, since that is a pretty decent size. And instead of printing an example out for you to copy, click on this link here:

Example Map First create a folder in the same directory as your exe called maps. This folder is where all your maps will be. Save that map as 1.map. Also, make another folder called tilesets in the same directory as your exe, and save the tileset above as 1.png.

Now that we have a file format for our maps, lets start designing the class that will render this thing. Open up CMap.h:
#ifndef _CMAP_H_
    #define _CMAP_H_

#include <SDL.h>
#include <vector>

#include "CTile.h"
#include "CSurface.h"

class CMap {
    public:
        SDL_Surface* Surf_Tileset;

    private:
        std::vector<CTile> TileList;

    public:
        CMap();

    public:
        bool OnLoad(char* File);

        void OnRender(SDL_Surface* Surf_Display, int MapX, int MapY);
};

#endif
A few of the basic things are here, OnLoad and OnRender. OnLoad does what you expect, loads a map from a file, and populates the TileList. The OnRender also does what you expect, properly puts each tile on the screen and draws them using the Surf_Tileset. Lets define what these functions do, now open up CMap.cpp:
#include "CMap.h"

CMap::CMap() {
    Surf_Tileset = NULL;
}

bool CMap::OnLoad(char* File) {
    TileList.clear();

    FILE* FileHandle = fopen(File, "r");

    if(FileHandle == NULL) {
        return false;
    }

    for(int Y = 0;Y < MAP_HEIGHT;Y++) {
        for(int X = 0;X < MAP_WIDTH;X++) {
            CTile tempTile;

            fscanf(FileHandle, "%d:%d ", &tempTile.TileID, &tempTile.TypeID);

            TileList.push_back(tempTile);
        }
        fscanf(FileHandle, "\n");
    }

    fclose(FileHandle);

    return true;
}

void CMap::OnRender(SDL_Surface* Surf_Display, int MapX, int MapY) {
    if(Surf_Tileset == NULL) return;

    int TilesetWidth  = Surf_Tileset->w / TILE_SIZE;
    int TilesetHeight = Surf_Tileset->h / TILE_SIZE;

    int ID = 0;

    for(int Y = 0;Y < MAP_HEIGHT;Y++) {
        for(int X = 0;X < MAP_WIDTH;X++) {
            if(TileList[ID].TypeID == TILE_TYPE_NONE) {
                ID++;
                continue;
            }

            int tX = MapX + (X * TILE_SIZE);
            int tY = MapY + (Y * TILE_SIZE);

            int TilesetX = (TileList[ID].TileID % TilesetWidth) * TILE_SIZE;
            int TilesetY = (TileList[ID].TileID / TilesetWidth) * TILE_SIZE;

            CSurface::OnDraw(Surf_Display, Surf_Tileset, tX, tY, TilesetX, TilesetY, TILE_SIZE, TILE_SIZE);

            ID++;
        }
    }
}

Bug Fix: There was a bug in the continue within the loop, we should be incrementing the ID before continuing. Thanks Zakalwe for helping find this bug!
Bug Fix: We should been dividing by TilesetWidth for TilesetY, otherwise it will not correctly grab the correct Y coordinate unless the Tileset is square. Thanks Anime for helping find this bug!

Lets start at the top. The constructor sets the Tileset to NULL, obviously. Next we have the OnLoad function. First, we are clearing out any old tiles, that way if we load twice, it won't have double the amount of tiles, effectively loading a new map. We then open up a FileHandle, and try to open the requested map file. Now, we go through the map file and grab each tile. This is accomplished by using the two loops. The outer loop is the Y axis loop, going from the top row to the bottom row of the map file. The inner loop is the X axis loop, going from the left most tile, to the right most tile. Pay attention to how this loop works, because it is also used below in the OnRender function. It basically goes from the top left tile, to the bottom right tile, 1 tile at a time. Inside of those loops we create a temporary tile, and load the file information into it. We then push that into our TileList, effectively saving the tile. We close the filehandle, and we are done.

Next, we have the function to render a map. Notice the MapX and MapY arguments. These tell use where to render this map on the screen. This is useful later on for moving maps around. First in the function we check for a valid tileset, because we are going to access this tileset directly and don't want to cause a crash. We then grab the TilesetWidth and TilesetHeight in Tiles. This is important, because we need to know how many tiles a tileset contains, not its actual width and height. That way we can match up a TileID to a tileset. So, a Tileset with a Width and Height, in tiles, of 2x2, would have 4 tiles on it, but in reality it would be 32x32 pixels. So, a TileID of 0 would match the first tile, a TileID of 1 would be the next, and so on. TileIDs repeat on the next row, from left to right.

Okay, next we have that same loop again, going through each tile (Please note that these X and Y coordinates here in the loop are also in number of tiles, they aren't actual pixels). This time, however, we have an ID variable. This ID is incremented by one each loop, and will allow us to grab each tile in the map. So, first we check to see if this Tile should even be drawn, by checking the TypeID. If it is TILE_TYPE_NONE, we skip over it with continue. After that we figure out where to draw this tile on the screen. This is done by taking the MapX and MapY (effectively offset coordinates, because they offset a map from 0, 0 to somewhere, which makes it seem like a map is moving), and taking the X and Y coordinates in the loop. We have to convert these X and Y coordinates back to pixel coordinates, which is done by multiplying it by the size of a tile.

After that, we do something with the Tileset. What we are doing is calculating where on the Tileset to grab the appropriate tile. This is by grabing the TileID of the tile first, and then converting that to a Tile coordinate. A little bit of explanation here. We have our 2x2 tileset, and a TileID of 1. Figuring out X, we would get 1 % 2, which would be 1 still. We when multiply that by the TILE_SIZE, and get 16. That is the correct X coordinate for the Tile. Same with the Y, we put 1 / 2, which is 0.5. Since this an integer operation, the .5 is automatically dropped. Thus we are left with 0. Which is also the correct row. Now, say we had a TileID of 2. 2 % 2 = 0, and 2 / 2 = 1. See how the X repeats in a pattern? 0, 1, 0, 1... And notice how Y increases every time it goes past the Tileset Width? 0, 0, 1, 1. I hope this is clear, as it is somewhat hard to explain.

Next, we actually draw the tile to the screen using the coordinates we just calculated, and then increase the ID to go to the next tile. A little side note here, we could, for the sake of speed, create an OnRender_Cache function that would perform this same operation, but would draw to a Surface defined in the CMap class. Something like SDL_Surface* Surf_Map. Then, the OnRender function would render the Surf_Map only, and not perform any operations. But also take note that that method does not necessarely work later on when we want to animate tiles.

Okay, cool! I know that is quite a bit to take in at once, but don't fret! It's really not too bad once you get the hang of it. I want to say that right now we could create Map objects and render maps just fine. This would be ideal for games like Mario and Megaman, as they are just one big map. But for games like Zelda and Metroid, it won't work too well. That is where Areas come in. Just like maps having file formats, each area will have its own file and file format. Each area is going to have a tileset to load, the size of the area, and the maps that are to be loaded in each area. Here is the area we are going to be using below:

./tilesets/1.png
3
./maps/1.map ./maps/1.map ./maps/1.map
./maps/1.map ./maps/1.map ./maps/1.map
./maps/1.map ./maps/1.map ./maps/1.map

Save this into a file called 1.area, and save it in your maps directory. Just like maps, this area will tile maps. I won't get into detail because I think you all have the point by now.

Open up CArea.h and lets begin:
#ifndef _CAREA_H_
    #define _CAREA_H_

#include "CMap.h"

class CArea {
    public:
        static CArea            AreaControl;

    public:
        std::vector<CMap>       MapList;

    private:
        int                     AreaSize;

        SDL_Surface*        Surf_Tileset;

    public:
        CArea();

        bool    OnLoad(char* File);

        void    OnRender(SDL_Surface* Surf_Display, int CameraX, int CameraY);

        void    OnCleanup();
};

#endif
This class is going to similar to the Map class in some ways, but different in others. Similarly, we have MapList that will hold our maps, just like the Map class had a list of tiles. Also, as have a Load function and Render function. Now the differences, we have a static AreaControl, much like Entities have a static control. This will allow us to manipulate an Area from any class by using this object. Next, we have the AreaSize which is the number of maps. We are going to assume that areas will always be square, so an AreaSize of 3, would be a 3x3 area. If you wanted to, and I don't think it's necessary, you would have AreaWidth and AreaHeight. Next, we have a surface for the Tileset. You probably noticed that within the CMap class we never actually loaded a tileset. That's because the Area is going to do it for us, and then pass that pointer to the CMap class. That way each map doesn't load a tileset for itself, but effectively all maps share the same tileset. Again, if you wanted each Map to have its own tileset you can easily modify the class to do so.

Okay, now open up the CArea.cpp class:
#include "CArea.h"

CArea CArea::AreaControl;

CArea::CArea() {
    AreaSize = 0;
}

bool CArea::OnLoad(char* File) {
    MapList.clear();

    FILE* FileHandle = fopen(File, "r");

    if(FileHandle == NULL) {
        return false;
    }

    char TilesetFile[255];

    fscanf(FileHandle, "%s\n", TilesetFile);

    if((Surf_Tileset = CSurface::OnLoad(TilesetFile)) == false) {
        fclose(FileHandle);

        return false;
    }

    fscanf(FileHandle, "%d\n", &AreaSize);

    for(int X = 0;X < AreaSize;X++) {
        for(int Y = 0;Y < AreaSize;Y++) {
            char MapFile[255];

            fscanf(FileHandle, "%s ", MapFile);

            CMap tempMap;
            if(tempMap.OnLoad(MapFile) == false) {
                fclose(FileHandle);

                return false;
            }

            tempMap.Surf_Tileset = Surf_Tileset;

            MapList.push_back(tempMap);
        }
        fscanf(FileHandle, "\n");
    }

    fclose(FileHandle);

    return true;
}

void CArea::OnRender(SDL_Surface* Surf_Display, int CameraX, int CameraY) {
    int MapWidth  = MAP_WIDTH * TILE_SIZE;
    int MapHeight = MAP_HEIGHT * TILE_SIZE;

    int FirstID = -CameraX / MapWidth;
        FirstID = FirstID + ((-CameraY / MapHeight) * AreaSize);

    for(int i = 0;i < 4;i++) {
        int ID = FirstID + ((i / 2) * AreaSize) + (i % 2);

        if(ID < 0 || ID >= MapList.size()) continue;

        int X = ((ID % AreaSize) * MapWidth) + CameraX;
        int Y = ((ID / AreaSize) * MapHeight) + CameraY;

        MapList[ID].OnRender(Surf_Display, X, Y);
    }
}

void CArea::OnCleanup() {
    if(Surf_Tileset) {
        SDL_FreeSurface(Surf_Tileset);
    }

    MapList.clear();
}
Quickly, going from the top, we declare the static object, and then set the AreaSize to 0. Then we have our Load function. It works just like the Load function within CMap, except there are a few differences. We have to load tileset first. We try to load this tileset into the surface, and if it fails return false. We then load the size of the Area. Okay, after that we have two loops, just like maps, that will go through and grab each map filename. It will then create a map object, load the map, set the tileset to be used, and then push it into the list. Pretty simply and straight forward.

Next, we have the Render function. We calculate the actual Map width and height, in pixels, first. This will let us find the first map to render. Some explanation first of what I am trying to do. Since an area can be of any size, like 100x100 maps, we don't want to go through all the trouble and render every single map. We only want to render the maps that are visible in the screen area. For our size of a screen, 640x480, only a possible 4 maps can be visible at one time (like standing at the corner of 4 maps). So what we have to do is calculate the first Map ID to grab. This is going to the first map to render. From the first ID, we know the next 3 maps to render. The one to the right of the first map, the one at the bottom of the first map, and the one at the bottom right of the first map. How is that accomplished? Look at the image below:

We have a guy, the circle, in the very center of the screen, represented by the red square. The other squares are each Map, and we have a Camera position of -700, -700. Why negative? Think about it, the screen itself never really moves, everything else does. So, for something to move up, it must increase its Y in the negative direction. Same with the X coordinate. So, to get to the 4th map, we have to move the area in the negative direction. Now, notice the grayed out maps, these are maps not visible within the players view, so they shouldn't be rendered. To figure out the First ID, which is 4 in this case, we need to use the specified Camera coordinates. We translate those Camera coordinates into Map coordinates. So, taking -(-700) / 640 (which is 40 * 16, remember the MapWidth calculation done above) we get 1 (dropping the decimal because its an integer operation). This is the X coordinate in Maps, but we aren't done. We then calculate the Y coordinate in Maps the same way -(-700) / 640, but we multiply that by the AreaSize. That's because we are grabbing the ID. So, it would become 1 * 3, which is 3, and adding that to the first calculation of 1, we get 4. And what do you know, the First ID on the map!

Okay, so we go through each of the four maps now. Since I did a regular loop of i < 4, we need to figure out how to add that to the First ID, to actually figure out each of the four Map IDs. This is done by taking the First ID as an offset first. We then take i, our position in the loop, divide by two and multiply by the area size. What does this do? Much like the Map class, it creates a pattern, 0, 0, 1, 1. Same with the last operation i % 2, it creates a pattern of 0, 1, 0, 1. This then gives us the correct pattern of, 4, 5, 7, 8. Which are the correct map to render.

We do a little check to make sure the ID is good, since the ID may not exist. And now the last calculation (yes, I know, a lot of complex calculations). It works just like the way we calculated which tile to grab on the tileset. It's turning an ID into actual pixel coordinates, and then offsetting those coordinates by the camera (making it seem like it moved). We then finally render the map, passing the coordinates of where to draw it.

Lastly, we have our cleanup function that frees the surface and clears the maps.

What a load! I hope all of the various calculations don't get you confused. There are really only two basic things we are trying to do, turn pixel coordinates into grid based coordinates. Such as with tiles, and with the maps. The other thing was turning those grid based coordinates back and forth into IDs.

Okay, we are not quite done yet, still a little bit to do (now you see why this tutorial took so long to complete!). You may have noticed that I called the arguments on the Render function on the CArea class CameraX and CameraY. We are going to need to make a camera class that defines the viewing area. This is what we are going to manipulate to move around on a map!

So create two new files, CCamera.cpp and CCamera.h. Open up the header file first:
#ifndef _CCAMERA_H_
    #define _CCAMERA_H_

#include <SDL.h>

#include "Define.h"

enum {
    TARGET_MODE_NORMAL = 0,
    TARGET_MODE_CENTER
};

class CCamera {
    public:
        static CCamera CameraControl;

    private:
        int X;
        int Y;

        int* TargetX;
        int* TargetY;

    public:
        int TargetMode;

    public:
        CCamera();

    public:
        void OnMove(int MoveX, int MoveY);

    public:
        int GetX();
        int GetY();

    public:
        void SetPos(int X, int Y);

        void SetTarget(int* X, int* Y);
};

#endif
First, we have a control member object, just like area. Then we have the coordinates of where the camera is. An extra thing I threw in, was the ability to target something. For example, in a Megaman game the camera would target Megaman himself. That way, when Megaman moves the camera would automatically update. So we have two pointers for X and Y coordinates. If these are null, the camera will automatically revert to the cameras position. Next, we have a target mode, which, for now, is either normal (the camera will position to the top left of the target) or center (will center the camera to the target). Pretty simply I think.

We then have a few functions, the first is the OnMove, which will increase the X and Y of the camera by MoveX and MoveY. So OnMove(-1, 0) would move the camera to the left one pixel. We then have Get functions for the coordinates, and the ability to set the coordinates and set a target.

Okay, now open up CCamera.cpp:
#include "CCamera.h"

CCamera CCamera::CameraControl;

CCamera::CCamera() {
    X = Y = 0;

    TargetX = TargetY = NULL;

    TargetMode = TARGET_MODE_NORMAL;
}

void CCamera::OnMove(int MoveX, int MoveY) {
    X += MoveX;
    Y += MoveY;
}

int CCamera::GetX() {
    if(TargetX != NULL) {
        if(TargetMode == TARGET_MODE_CENTER) {
            return *TargetX - (WWIDTH / 2);
        }

        return *TargetX;
    }

    return X;
}

int CCamera::GetY() {
    if(TargetY != NULL) {
        if(TargetMode == TARGET_MODE_CENTER) {
            return *TargetY - (WHEIGHT / 2);
        }

        return *TargetY;
    }

    return Y;
}

void CCamera::SetPos(int X, int Y) {
    this->X = X;
    this->Y = Y;
}

void CCamera::SetTarget(int* X, int* Y) {
    TargetX = X;
    TargetY = Y;
}
From top to bottom, like usual, we have are static member first. We then have the constructor defaulting some variables. The OnMove function will increase the X and Y, like I said before. We then have GetX. It will check to see if we have a valid target, and if so return the targets coordinates as the camera coordinates, otherwise return the camera x. For centering mode, we take the screen width and divide by two to find the center of the screen, and then subtract it from the target coordinates. This will center the camera to the target. We then have the SetPos and SetTarget functions, which are self-explanatory.

So lets put all this junk together! Open up CApp.h and modify it to include the new header files:
#include "Define.h"

#include "CArea.h"
#include "CCamera.h"
Also, add a new function prototype:
void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);
We are going to checking that event for move our map around with the keyboard. So, open up CApp_OnEvent.cpp and add the function:
void CApp::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
    switch(sym) {
        case SDLK_UP:      CCamera::CameraControl.OnMove( 0,  5); break;
        case SDLK_DOWN:  CCamera::CameraControl.OnMove( 0, -5); break;
        case SDLK_LEFT:  CCamera::CameraControl.OnMove( 5,  0); break;
        case SDLK_RIGHT: CCamera::CameraControl.OnMove(-5,  0); break;

        default: {
        }
    }
}
We check the SDL keys, and according to the key move the camera around. Next, open up CApp_OnCleanup.cpp and add the cleanup function for the Area:
CArea::AreaControl.OnCleanup();
Open up CApp_OnRender.cpp and add the Render call too:
CArea::AreaControl.OnRender(Surf_Display, CCamera::CameraControl.GetX(), CCamera::CameraControl.GetY());
Notice that we are passing the Camera coordinates to the OnRender. And lastly, open up CApp_OnInit.cpp:
if(CArea::AreaControl.OnLoad("./maps/1.area") == false) {
    return false;
}

SDL_EnableKeyRepeat(1, SDL_DEFAULT_REPEAT_INTERVAL / 3);
SDL_EnableKeyRepeat call sets the keyboard repeat count. So if we hold down a key it will keep calling the event above. And we are done! Compile the code and try it out. I hope it all works as expected, this was a very lengthy tutorial. Please check out the tutorial files below if you are having a bit of trouble. And please let me know if for some reason I left some important code out.