Since most tile maps break down into something like this:
10,20,12,23,45,67,1,2,8,10,15,15,15,15,15,0,0,0,0, 0,..................
Streamed level loading is an absolute waste of time. Unless you are writing a major terrain engine or some type of flight simulator that cannot possibly keep all the data in memory at once, then a streamed loader is what you need. But most games do NOT load on the fly.
Face it, the hard disk even as fast as it is today is still a slow crawl compared to a memory access. There are two things you don't want to do in any game.
1. You don't want the program swapping to the hard drive. Major lag here and major frame rate issues.
2. You don't want to load on the fly. Go out and purchase some flight sims. Even the pro's cannot load in new terrain and object data w/o some type of frame rate hit or major lag in multiplayer. It just cannot be done.
Here is a tile map class. This demonstrates how loading in a tile map actually lends itself more towards array based file I/O than character based streamed I/O. I wouldn't use streams at all - the C standard library will take care of this quite nicely for you.
Code:
#ifndef CTILEMAP
#define CTILEMAP
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <io.h>
struct CTilemapHeader
{
char Signature[2];
char Author[40];
char Date[10];
char Time[5];
WORD Width;
WORD Height;
DWORD Size;
};
class CTilemap
{
CTilemapHeader m_pMapInfo;
WORD *m_pMapData;
public:
CTilemap(void):m_pMapData(NULL) {}
virtual ~CTilemap(void) {if (m_pMapData) delete [] m_pMapData;};
bool Load(std::string File)
{
//Open file
int handle=_open(File.c_str(),_O_RDONLY | _O_BINARY,_S_IREAD);
//Check for valid handle
if (handle!=-1)
{
read (handle,&m_pMapInfo,sizeof(CTilemapHeader));
//Check for signature in file, bail on failure
if (m_pMapInfo.ZeldaSig[0]!='Z' && m_pMapInfo.ZeldaSig[1]!='A')
{
close(handle);
return false; //Not a zelda map
}
//Allocate memory
m_pMapData=new WORD[m_pMapInfo.Size];
//Make sure pointer is valid
if (m_pMapData)
{
read(handle,(WORD *)m_pMapData,m_pMapInfo.Size);
} else return false; //pointer is not valid
} else return false; //File handle is not valid
return true; //Map loaded successfully
}
WORD GetData(WORD column,WORD row)
{
DWORD offset=(row*m_pMapInfo.Width)+column;
if (offset<m_pMapInfo.Size)
{
return m_pMapData[offset];
} else return (0xFFFF);
}
WORD GetData(DWORD offset)
{
if (offset<m_pMapInfo.Size)
{
return m_pMapData[offset];
} else return (0xFFFF);
}
WORD *GetMap(void)
{
if (m_pMapData)
{
return m_pMapData;
} else return (NULL);
}
};
#endif
That's really all you need for any scroller.
When you begin you are at offset 0 into the tilemap. So the side scroller render function will draw the tile at offset 0, and then increment the offset and the x position...draw the next, etc.
When you reach the right side of the screen, you need to increment by the total width of the tilemap or the famous offset=(row*width)+column. If you increment your offset by (width) then you will maintain the correct positioning in the array.
This is all done in linear fashion and is a simple matter of incrementing values. No need to re-compute offsets or do expensive multiplies mid-function.
Tile system:
0. Start at top-left of screen.
1. Get current tile map (array)
2. Iterate through tile map.
3. Pull value from tile map, access texture based on this number
4. Render Texture
5. Increment offset, x, and if necessary y.
6. Goto 2 until we have reached right side bottom of screen.
For scrolling, this is a simple matter of changing the on-screen grid. Increment the starting offset when (x % tilewidth) or (y % tilewidth) is 0.