Best way to do a tile map?

Started by
7 comments, last by programering 18 years, 7 months ago
Hey everyone I was wondering what the best way to do a tile map for a side-scrolling gmae is. Do you use a 2 dimensional array? Do you just use normal screen coordinates and translate that into array dimensions? I've seen some tutorials but I was wondering if anyone could let me know what they think is best and if they have any particularly good tutorials, please share!!! Thanks :)
Advertisement
Something like this?

int mapCols;int mapRows;int mapWidth;int mapHeight;Tile *mapTiles;Tile **mapTileRows;void InitMap(void){    mapTileRows = new Tile*[mapRows];//  Tile *tileRow = mapTiles;    for (int row=0; row < mapRows; row++)    {        mapTileRows[row] = mapTiles + (row * mapCols);    }}



Good luck!!!
have a one dimensional array for all tiles. More dimensions will be used for different layers of tiles. Now for the tiles that are drawn on the screen its easy to calculate which tile (x, y) is in the corners of the screen. By knowing that draw the tiles away. And draw the sorrounding tiles just incase you might see "garbage" on the sides of the screen.

For enemy's Items and etc. Use the same method but since enemy's could be in between tiles store the enemy's coordinates. Have a linked list of all active enemy's and items. And just update that list when the player has scrolled enough. Enough could be scrolling one to 3 tile lengths.

Thats how I have done it before.

Enjoy!
Sorry can someone explain this a little furthur I'm still a little confused on how a one dimension array for a 2-dimensional object (the map) would work...

Then again, I am really tired :-/ Gah, next semester need to set my schedule to get up later.

using that system how would one draw a tile to the screen?

and in programering's example you have:

mapTileRows[row] = mapTiles + (row * mapCols);

wouldn't row * mapCols give you the whole row of columns, hmmm sorry pretty confused; maybe ill understand more when i look at it later, right now, off to class


Thanks every1 :)
It's a loop, the first time row is 0, then it increases by 1 next time

for (int row=0; row < mapRows; row++)
{
mapTileRows[row] = mapTiles + (row * mapCols);
}

You allocate the map tiles I forgot that so it should look like this:
#define WORLD_UNIT 40  // for examplevoid InitMap(int cols, int rows){    mapCols = cols;    mapRows = rows;    mapWidth = mapCols * WORLD_UNIT;    mapHeight= mapRows * WORLD_UNIT;    mapTileCount = mapCols * mapRows;    mapTiles = new Tile[mapTileCount];    mapTileRows = new Tile*[mapRows];//  Tile *tileRow = mapTiles;    for (int row=0; row < mapRows; row++)    {        mapTileRows[row] = mapTiles + (row * mapCols);    }}


Like this:
Say that your tile map is 5 rows height and 7 cols wide for example.
then mapRows is 5 and mapCols is 7
--------------------------------------
mapTileRows[0] = mapTiles + (0 * 7);
mapTileRows[0] = base tile buffer address + 0 offset

mapTileRows[1] = mapTiles + (1 * 7);
mapTileRows[1] = base tile buffer address + 7 offset

mapTileRows[2] = mapTiles + (2 * 7);
mapTileRows[2] = base tile buffer address + 14 offset

mapTileRows[3] = mapTiles + (3 * 7);
mapTileRows[3] = base tile buffer address + 21 offset

mapTileRows[4] = mapTiles + (4 * 7);
mapTileRows[4] = base tile buffer address + 28 offset
---------------------------------------

The tile map indexes:
-----------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 |10 |11 |12 |13 |
|14 |15 |16 |17 |18 |19 |20 |
|21 |22 |23 |24 |25 |26 |27 |
|28 |29 |30 |31 |32 |33 |34 |
-----------------------------

Your welcome!!!
ok i think i mostly get it...i dunno :/

although i haven't actually done any map loading yet this is how i would do it...

assume MAPHEIGHT and MAPWIDTH are constants and that the world size is say 100 tiles by 15 (5 screens long of 640)

assuming i did it the old way and used a huge array i would do somethin like this:

for (int mwidth;  mwidth < MAPWIDTH; mwidth++){   for(int mheight = 0; mheight < MAPHEIGHT; mheight++)   {       // output map to screen using map[mheight][mwidth]   }}


but anyway, that's how i would do it...

i tried to figure this out in vector calculus, and i sorta get what is goin on, however... not completely sure

first of all in programering's example i'm not sure what mapTiles represents you say it is you base tile buffer address... what do you mean?
i totally see the rows, however how would you compute the columns?

all i can figure from it is something like this:


    for (int row=0; row < mapRows; row++)    {        for(int col=0; col < mapCols; col++)        {          mapTileRows[row] = cols + (row * mapCols);        }    }


b/c i don't see how you are getting your width... in your example:

Quote:
The tile map indexes:
-----------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 |10 |11 |12 |13 |
|14 |15 |16 |17 |18 |19 |20 |
|21 |22 |23 |24 |25 |26 |27 |
|28 |29 |30 |31 |32 |33 |34 |
-----------------------------


the only thing i can think is that the mapTiles is your width...



ahhhh i'm confused... i shouldn't be, this is probably easy but i'm not gettin it, gah, i can do calc 3 but no tiles... so sad


thanks for your help programering, you get more rating =)
The mapTiles is the array of all your tiles in my example.
You mem alloc the whole chunk of tiles the tile columns and rows together:

mapTileCount = mapRows * mapCols;
mapTiles = new Tile[mapTileCount];

Then mapTileRows is the an double **pointer array buffer that
You memalloc of as many elements with mapRows.
You memalloc mapTileRows with as many elements with mapRows,
Then each element of mapTileRows * is a pointer to where
mapTiles begins the new row at left.
You mem alloc a double** pointer array that holds the address to where mapTiles offset with mapCols at left begins each new row.

mapTileRows = new Tile*[mapRows];
for (int row=0; row < mapRows; row++)
{
// here you assign the address offsets to each new row begins
mapTileRows[row] = mapTiles + (mapCols * row);
}


Then to get the tiles in the row and column position:
Here's the first way to do it without the mapTileRows **array:
Tile *GetTile(Uint16 row, Uint16 col){    Tile *tile = NULL;  // initial value of NULL    if (row < mapRows)  // Check if the given row index    {                   // is within our maps row range        if (col < mapCols) // Check if the given col index         {                  // is within our maps col range            tile = mapTiles + ((row * mapCols) + col);            // then here we assign the tile pointer to            // to point on the tile in the mapTiles array that            // is on the corresponding row and col index        }    }    return tile;}



Here's the other way to do it with
the **mapTileRows array, it's an optimation.
Tile *GetTile(int row, int col){    Tile *tile = NULL;  // initial value of NULL    if (row < mapRows)  // Check if the given row index    {                   // is within our maps row range        if (col < mapCols) // Check if the given col index         {                  // is within our maps col range            tile = mapTileRows[row] + col;            // then here we look up the pointer to mapTiles            // where the new row begins plus the number of columns to offset        }    }    return tile;}
There is certainly no calculus required for this [grin] .


If you want to use a 1D array to hold map data, you (as you can figure out) need an array size MapWidth*MapHeight. To use programering's example a 7x5 map, youd create a Tile* MyMap = new Tile[7*5];
Quote:
The tile map indexes:
-----------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 |10 |11 |12 |13 |
|14 |15 |16 |17 |18 |19 |20 |
|21 |22 |23 |24 |25 |26 |27 |
|28 |29 |30 |31 |32 |33 |34 |
-----------------------------


You need a formula to get the index from a given row and column. (IE MyMap[someformula] = MyTile)

The way I tend to figure things like this out is with a few examples, and think of the simplest formula that will work.

Think of the index of row: 0 and column: 0? Row 1 and column 0? Row 0 and column 1? Try to think of a formula that will get you the correct index, given only "row" and "column" as inputs.

In the end, you get MyMap[row*MapWidth + column] = MyTile.


Now, as programering said, the further things he did were optimizations. Once you understand the first part well, you can re-read it. Basically he saves the memory address of each row, just once, to avoid having to do multiplications per tile in every frame.
And one more thing.
I've figured out how to load the map
from a text file with indicators.
From my current project: Tile Quest Engine.

But we can't have just the tiles
we must have elements instead of tiles.
The element definition:
typedef Uint8 Element; // the ElementDef definition array indexstruct ElementDef {    Uint8 type; // the element type    Tile *tile;    char *name;};enum {    _element_void,    _element_ground,    _element_media};#define NUMBER_OF_ELEMENTS 4struct ElementDef mapElementDefs[NUMBER_OF_ELEMENTS] ={    {_element_void, NULL, "Void/Air"},    {_element_ground,&mapTiles[0],"Stone"},    {_element_ground,&mapTiles[1],"Dirt"},    {_element_media, &mapTiles[2],"Water"}};


V stand for Void, it's the air, the walkable
elements where you can also see the background.
S for Stone for example.
D for Dirt.
and W for Water.

Here's the indicator definition:
const char mapIndicators[NUMBER_OF_ELEMENTS] = {'V','S','D','W'};


Here's the map file level.map:
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVSSDDSSSSVVSSSSSSSSSSVVVVVVVVSSSSSSDDDDDDDDDDDDDDDDDDDDWWWWWWWWDDDDDDDDDDDDDDDDDDDDDDDDDDDWWWWWWDDDDDDD


Here's the code for loading it:
void LoadMap(const char *filename){    bool success;    unsigned long map_length;    char *map_data = GetFileChars(filename,&map_length);    if (map_data)    {        char *ch = map_data;        mapElements = new Element[map_length];        Element *element = mapElements;        usigned short col=0,row=0;        for (unsigned long file_pos=0; file_pos < map_length; file_pos++)        {            if (*ch == '\n') { row++; col=0; }            else            if (*ch != '\r')            {                for (int i=0; i < NUMBER_OF_ELEMENTS; i++)                    if (*ch == mapIndicators) *element = i;                col++;                element++;            }            ch++;        }        mapCols = col;        mapRows = row + 1;        mapLength = mapCols * WORLD_UNIT;        mapHeight = mapRows * WORLD_UNIT;        mapElementRows = new Element*[mapRows];        for (row = 0; row < mapRows; row++)            mapElementRows[row] = mapElements + (mapCols * row);        free(map_data);        success = true;    }    else    {        success = false;        fprintf(stderr,"Couldn't load map file: &s\n",filename);    }    return success;}char *GetFileChars(const char* filename, unsigned long *size){    char *buffer;    FILE *pFile = fopen(filename,"r");    if (pFile)    {        fseek(pFile,0,SEEK_END); // I'm not sure here        *size = ftell(pFile);        fseek(pFile,0,SEEK_SET);        fread(buffer,*size,1,pFile);        fclose(pFile);    }    else    {        buffer = NULL;        fprintf(stderr,"Couldn't read file char buffer in file: %s\n"                       "It probaly don't exists\n",filename);    }    return buffer;}

This topic is closed to new replies.

Advertisement