|
Kindly hosted by

Tile Engine Tutorial - Introduction
Table of Contents
Welcome to the first tutorial in the Tile Engine series. On completetion of these tutorials you will know how to construct
an advanced tile engine with an effective and flexible data control and management.. No, I'm just joking. You'll learn how to
basically make a tile map. That's it. Let's start.
So, let's start.
What the hell?
The very first question you might ask me: Why the hell we need another tutorial on Tile Map while we already have heaps
of them online? Well, YOU will need one to understand the other way of coding a tile map, an effective and 'cool' way of
laying out classes, so that they will work seamlessly together to achieve one goal - to display a ... tile map.
OK, as yo might correctly guessed, our main class we dance from is a .. CMap class. Let's see what we will need in it
for proper functioning:
- Pointer to pointer of tiles (By the way, you might guessed correctly, the tile is a .. CTile class) - m_ppMap series.
- The tile width/height (if they differ) - m_uiTileWidth & m_uiTileHeight
- Number of tiles on the map (X Tiles and Y Tiles) - m_uiTilesX & m_uiTilesY
And also some functions to.. work:
- Create function that will create(allocate) tiles in accordance to tile width/height and tiles X/Y
- Load function that will load the map by either filename or reference to data
- Save function that will save our map to given file
- Draw function that will display our map (e.g draw the tiles)
- Destroy function that will clean up resources used by map
- And finally, the Resize function that will resize the map AND preserve the existing tiles
- The last function will be the Parse function that will load our tiles (I will cover it in a few moments)
Well, so far so good? Let's now go more into the very interesting stuff called 'templates'.
I have created a class called CManager that accepts a template value. All that class does is implements a database for the
template class, so that you can search the item you add to it by either index, name or Database Unique ID. Fun stuff, isn't it?
Let's see how it looks like:
template<class T> class CManager
{
private:
struct tContainer
{
T *m_pEntry;
unsigned int m_iuDbID;
char m_strName[64];
tContainer()
{
m_pEntry = NULL;
m_iuDbID = 0;
for (int i=0; i<64; i++)
m_strName[i] = 0;
};
};
unsigned int m_uiLastDbID;
public:
CManager()
{
m_uiLastDbID = 0;
};
virtual ~CManager()
{ Destroy(); };
virtual int Parse(TiXmlNode *this_node, char *filename = NULL) = 0;
virtual T *Get(int index, int DbID = -1, char *name = NULL)
{
if(DbID < 0 && name == NULL)
{
if(index >=0 && index < m_lstEntries.size())
return m_lstEntries.at(index);
}
else if(index == -1 && name == NULL)
{
tContainer *tmp = m_lstDatabase.begin();
m_lstDatabase.set_ptr(tmp);
while(tmp !=NULL)
{
if(tmp->m_iuDbID == DbID)
return tmp->m_pEntry;
tmp = m_lstDatabase.next();
}
}
else if(DbID == -1 && index == -1)
{
tContainer *tmp = m_lstDatabase.begin();
m_lstDatabase.set_ptr(tmp);
while(tmp !=NULL)
{
if(stricmp(tmp->m_strName, name) == 0)
return tmp->m_pEntry;
tmp = m_lstDatabase.next();
}
}
return NULL;
};
virtual int Add(T *Entry, int DbID = -1, char *name = "")
{
T *tmp = m_lstEntries.begin();
m_lstEntries.set_ptr(tmp);
while(tmp !=NULL)
{
if(tmp == Entry)
return 0;
tmp = m_lstEntries.next();
}
m_lstEntries.push_back(Entry);
tContainer *Container = new tContainer;
Container->m_pEntry = Entry;
if(DbID < 0)
{
Container->m_iuDbID = m_uiLastDbID;
m_uiLastDbID++;
}
else
{
bool AlreadyHasID = false;
tContainer *tmp = m_lstDatabase.begin();
m_lstDatabase.set_ptr(tmp);
while(tmp !=NULL)
{
if(tmp->m_iuDbID == DbID)
{
AlreadyHasID = true;
break;
}
tmp = m_lstDatabase.next();
}
if(AlreadyHasID == true)
{
Container->m_iuDbID = m_uiLastDbID;
m_uiLastDbID++;
}
else
Container->m_iuDbID = DbID;
}
if(name !=NULL || strlen(name) !=0)
strncpy(Container->m_strName, name, 64);
m_lstDatabase.push_back(Container);
return m_lstDatabase.size();
};
virtual int Remove(T *Entry)
{
tContainer *tmp = m_lstDatabase.begin();
m_lstDatabase.set_ptr(tmp);
while(tmp !=NULL)
{
if(tmp->m_pEntry == Entry)
tmp->m_pEntry = NULL;
tmp = m_lstDatabase.next();
}
delete m_lstEntries.remove(Entry);
return 1;
};
unsigned int GetID(T *Entry)
{
tContainer *tmp = m_lstDatabase.begin();
m_lstDatabase.set_ptr(tmp);
while(tmp !=NULL)
{
if(tmp->m_pEntry == Entry)
return tmp->m_uiDbID;
tmp = m_lstDatabase.next();
}
return 0;
};
char *GetName(T *Entry)
{
tContainer *tmp = m_lstDatabase.begin();
m_lstDatabase.set_ptr(tmp);
while(tmp !=NULL)
{
if(tmp->m_pEntry == Entry)
return tmp->m_strName;
tmp = m_lstDatabase.next();
}
return 0;
};
int GetDbCount()
{ return m_lstDatabase.size(); };
int GetCount()
{ return m_lstEntries.size(); };
virtual void Destroy()
{
ClearDatabase();
ClearEntries();
};
private:
CDoubleList<tContainer *> m_lstDatabase;
CDoubleList<T *> m_lstEntries;
protected:
void ClearDatabase()
{ m_lstDatabase.erase(); };
void ClearEntries()
{ m_lstEntries.erase(); };
};
I'm not going to go into intricate details of its work because its extremely easy for you to implement a similar thing.
Instead, let's see the declaration of our map:
#include "Tile.h"
class CMap : public CManager<CTile> // : public CFrustum
{
// ...
};
It's very simple. This means that our map will contain a number of entries of CTile type. We need that so that we can easily
add/remove/'you-name-it' TEMPLATE tiles that are going to be copied to the tiles we need by using = operator (that is going
to be overloaded
by the way!). This will save us HEAPS of time because we will have to simply copy the data over, not
open/close file over and over again, seek for appropriate entry and load it. 'Yeah, piece of cake!' (Duke Nukem 3D) We will go into parsing the data of Map and Tile next tutorial. What I want to mention is that this is not the only CManager
derived class. There's also other managers, such as Texture, Material and Animation managers in the project, but you
don't have to touch them as far as you know the way they work. And they work in the same way, because they are all
derived from CManager class, thus inheriting the functions they mush override. Well, hmm.... I'll show how to use them soon
enough, just be patient please!
Classes Layout
Well, the classes layout is pretty straightforward since its just a.. umm.. tile map. So, here we go.
- CMap - Topmost class that has the responsibility of loading/parsing and rendering tiles
- CTile - a basic element of the map, this class performs in the way it supposed to perform. Hmm, not clear enough?
Well, it parses the data from file, animates itself by using CAnimatedTexture class and draws itself
with the texture corresponding to the animation sequence texture.
- CAnimatedTexture - an animated texture class (obviously!) that loads itself and animates when called.
- Managers - Texture, Material and Animation managers goes through whole data looking for signatures
of the data they have to load, load it but avoid duplicates.
- Logger - well, it logs data when asked to to log.. you ask it to..
That's about it. Oh yeah, there's also Math and accessory classes such as CDoubleList, CSingleton, CVector3 etc.
Done? Good, next!
OR
Biggest in Width
Biggest in Height
Or anything you like them to be aligned to. Piece of cake. Off you go!
More Info
As I now understand, XML data storage/retrieval is superior to any .INI or Plain Text data, so I'm going to use it throughout the
whole project. But if you don't like it, please feel free to ask me or modify the code yourself (the loading functions) to suit your needs.
I'm using TinyXML as an XML parsing tool. So, you want even more info? OK.
We are going to have 255 different tiles. Too many or too few? To explain why, I will say only one word: byte image. That makes, umm,
two words, but anyway, we are going to use 8-bit image (greyscale) to describe our map. So, for example, if our water tile is going to be
of a ID thas is 128, then on the
image it will be .. what? - middle grey color!
The Tile Engine we're going to make will have the following capabilities:
- Load the appropriate Material and Texture databases from any external or already opened XML file
An example for this is:
<Map TilesX="10" TilesY="10" TileWidth="50" TileHeight="50" TileMapPath="Data/Map.raw"
TexturesDatabase="Data/XML/mat_tex.xml" MaterialsDatabase="mat_tex.xml">
As you may see, it has all the required parameters for the map.
- Load template tiles as defined:
<Map TilesX="10" TilesY="10" TileWidth="50" TileHeight="50" TileMapPath="Data/Map.raw"
TexturesDatabase="Data/XML/mat_tex.xml" MaterialsDatabase="mat_tex.xml">
<Tile Type="Template" Filename="Data/XML/WhiteTile.xml"/>
<Tile Type="Template" Filename="Data/XML/BlackTile.xml"/>
<Tile Type="Template" Filename="Data/XML/GreyTile.xml"/>
</Map>
OK, the template tiles will be defined by Type value (if any). Here we are using the
reference to the tile data:
<Tile ID="0">
<Animation Materials="1" Frames="1" RefID="0">
<Frame Id="0" MatByRefID="10"/>
</Animation>
</Tile>
That's right! The tile will still have an ID, and only then it will have animation(s) it has.
- We will load materials/textures in the exact same way as they are usually loaded. It all is going to be explained
in the next tutorial.
Finishing touches
Well, the finishing touches are.. Go and read the next tutorial!
|