2d Tiles and Maps Tutorial

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
dmbush
Posts: 6
Joined: Fri Jul 25, 2008 11:46 pm

2d Tiles and Maps Tutorial

Post by dmbush »

I've been browsing these forums for a few weeks now, and feel that it's time I gave something back to the community. I hope some of you might find this useful. Also I wasn't sure whether to post this in snippets or tutorials section, so if someone thinks this should be moved by all means do so :).

You are welcome to use any of my code in any of your applications commercial or non commercial. Just be nice and leave my name in the file, thats all. Furthermore I am not liable for anything that should happen to you or your computer, directly or indirectly caused by the execution of my code. Including but not limited to spilling soda all over your keyboard.

Ok... on to the goods stuff. For a little while now I have been developing a 2d tile and map rendering engine. I thought I'd share my code with those looking to implement these techniques into their game using irrlicht. I am also open to comments and suggestions about my code (just polite suggestions though please). If you know a way to improve my code, I'd love to hear it.

Theres A lot of code here so I'm going to reserve the first few posts to split my code into sections. Hope you enjoy :)

Oh and a WARNING, for purposes of clarity of the tutorial my code is devoid of a lot of necessary error checking. You are responsible for adding this in yourself! (It should be pretty obvious where).
Last edited by dmbush on Tue Aug 19, 2008 1:13 am, edited 3 times in total.
dmbush
Posts: 6
Joined: Fri Jul 25, 2008 11:46 pm

Post by dmbush »

Part 1:

What is a tile?

Those of you who already know can feel free to skip this paragraph. Many games will create worlds by piecing together smaller images referred to as "tiles." Generally these tiles are squares (though they can also be diamonds, hexagons etc...). If you have a skilled artist, when you put the tiles together, you won't even be able to see them. This technique is most commonly used in 2d RTS and RPG style games.

I know what a tile is, how do I use them in my games?

Well, thankfully irrlicht easily handles the loading and drawing of 2d images. So it is up to us simply to create a data structure to store tiles, the information about them and how to piece them together (maps).

Ok enough blah blah blah, get to the code please!

Fine! The first task is to create a data structure to load and store the tile images to be drawn. I called mine TileManager.

here is TileManager.h

Code: Select all

/*
7/31/08
TileManager.h
Dan Bush
*/

#ifndef TILE_MANAGER_H
#define TILE_MANAGER_H

#include <irrlicht.h>
#include <string>
#include <vector>

using namespace irr;
using namespace video;
using namespace std;

// structure that stores the images and tile information
//for each texture we load
struct sTileSet
{
	ITexture * texture;
	unsigned short width;
	unsigned short height;
	unsigned short collumns;
};


class TileManager
{
private:

	vector<sTileSet> tSet;

public:
	TileManager();
	~TileManager();

	bool load(IVideoDriver * p, char * s, const unsigned short w, const unsigned short h, const unsigned short c, SColor key);
	bool free();
	bool draw(IVideoDriver * pDriver, const unsigned short texNum, const unsigned short tileNum, const short x, const short y, irr::core::rect<s32> * r = 0);
	
	short getNumTileSets();
	sTileSet getTileSetAt(const unsigned short i);
};


#endif

and of course its friend TileManager.cpp :)

Code: Select all

/*
7/31/08
TileManager.cpp
Dan Bush
*/

#include "TileManager.h"
#include <string>
#include <iostream>
#include <vector>

using namespace irr;
using namespace video;
using namespace std;
using namespace core;

//TileManager()
TileManager::TileManager()
{
	
	free();
		
}

//~TileManager()
TileManager::~TileManager()
{
	free();
}

//load()
//loads a tileset given a texture, the width and hieght of the tiles, the number of collumns
//and the transparent color key
bool TileManager::load(IVideoDriver * p, char * s, const unsigned short w, const unsigned short h, const unsigned short c, SColor key)
{	
	sTileSet set;

	set.texture = p->getTexture(s);
	set.width = w;
	set.height = h;
	set.collumns = c;
	tSet.push_back(set);


	p->makeColorKeyTexture(tSet[tSet.size() - 1].texture, key);


	return true;
}

//free()
bool TileManager::free()
{
		//dealloc the vector
		tSet.clear();
		return true;
}



//draw()
//draws a tile at x, y
//you probably wont call this directly very often (unless maybe you're writing an editor)
bool TileManager::draw(IVideoDriver * pDriver, const unsigned short texNum, const unsigned short tileNum, const short x, const short y, irr::core::rect<s32> * r)
{

	// get the source coordinates
	int sx = (tileNum % tSet.at(texNum).collumns) * tSet.at(texNum).width;
	int sy = (tileNum / tSet.at(texNum).collumns) * tSet.at(texNum).height;


	pDriver->draw2DImage(tSet.at(texNum).texture,position2d<s32>(x,y), rect<s32>(sx, sy, sx + tSet.at(texNum).width, sy + tSet.at(texNum).height),
						r, SColor(255,255,255,255), true);
	
	return true;
}

//getNumTileSets()
short TileManager::getNumTileSets()
{
	return tSet.size();
}

//getTileSetAt()
sTileSet TileManager::getTileSetAt(const unsigned short i)
{
	if(i >= tSet.size())
		return tSet[tSet.size()-1];

	else
		return tSet[i];
}

Ok how to use this? Lets say we have a 320 x 320 texture of 32x32 tiles called "tiles.png" and we want the color pink to be transparently drawn.

Code: Select all

TileManager t;

//load texture with 32x32 tiles and 10 collumns
//pDriver is a pointer to IVideoDriver
t.load(pDriver, "tiles.png", 32, 32, 10, SColor(SColor(255,255,0,255));


//this part would go between your beginscene and endscene
t.draw(pDriver, 0, 3, 0, 0); //draws tile #3 coordinates (64,0) from texture at (0,0) on the screen.
t.draw(pDriver, 0, 11, 64, 64); //draws tile #11 coordinates (0,32) from texture at (64,64) on the screen.
Stay tuned, in the next section we will start talkin about maps!
Last edited by dmbush on Tue Aug 19, 2008 12:06 am, edited 2 times in total.
dmbush
Posts: 6
Joined: Fri Jul 25, 2008 11:46 pm

Post by dmbush »

Ok I know how to draw cache and draw tiles now! Lets draw maps

Well actually, I lied we're not going to learn about maps in this section. We're going to learn about the individual sections of maps that I like to call nodes. A map node is essentially a tile, (or group of tiles drawn on top of eachother) at a certain location on the map. So to represent your map you would have two dimensional array, or vector, or whatever, of nodes that represent the word.

Each node contains the tile information as well as other custon information you can store for your own purposes. I have provided a character block that you could use to store bitflag information about each map node. Things like... you can't walk here, there is a trigger here, etc.

Just shut up and show us the code please

!!!!! *sigh* heres cTileMapNode.h

Code: Select all

/*
8/11/08
cTileMapNode.h
Dan Bush
*/


#ifndef TILE_MAP_NODE_H
#define TILE_MAP_NODE_H

#include <vector>
#include "TileManager.h"
#include <irrlicht.h>

using namespace irr;
using namespace std;

struct sTile
{
	unsigned char tileset;
	unsigned char id;

	sTile(unsigned char t, unsigned char i){tileset = t; id = i;}
};


class cTileMapNode
{
private:
	vector<sTile> tiles;
	unsigned char bitflags;	//use for whatever you want :) e.g. maybe setting bit 1 means you can't walk there


public:
	cTileMapNode();
	~cTileMapNode();

	bool addTile(sTile t);
	bool removeTile();

	bool free();

	bool draw(IVideoDriver * driver, TileManager * pImages, int x, int y, irr::core::rect<s32> * r = 0);

	unsigned char getFlags();
	void setFlags(const unsigned char c);
	short getNumTiles();
	sTile getTileAt(const unsigned short num);


	
};



#endif
So you call one file cTileMapNode but you don't call the other cTileManager, thats not very consistent

dont ask, heres cTileMapNode.cpp

Code: Select all

/*
8/11/08
cTileMapNode.cpp
Dan Bush
*/
#include "cTileMapNode.h"
#include <irrlicht.h>

using namespace irr;
using namespace video;

//cTileMapNode
cTileMapNode::cTileMapNode()
{
	free();
	bitflags = 0;
}

//~cTileMapNode()
cTileMapNode::~cTileMapNode()
{
	free();
}

//free()
bool cTileMapNode::free()
{
	tiles.clear();
	return true;
}

//addTile()
bool cTileMapNode::addTile(sTile t)
{
	tiles.push_back(t);
	return true;
}

//removeTile()
bool cTileMapNode::removeTile()
{
	if(!tiles.empty())
		tiles.erase(tiles.end() - 1);
	return true;
}

//draw()
bool cTileMapNode::draw(IVideoDriver * driver, TileManager * pImages, int x, int y, irr::core::rect<s32> * r)
{

	std::vector<sTile>::iterator itr;
	for ( itr = tiles.begin(); itr != tiles.end(); ++itr )
	{
		pImages->draw(driver, (*itr).tileset, (*itr).id, x, y, r);
	}

	
	return true;
}


unsigned char cTileMapNode::getFlags()
{
	return bitflags;
}



//getNumTiles()
short cTileMapNode::getNumTiles()
{
	return tiles.size();
}


sTile cTileMapNode::getTileAt(const unsigned short num)
{
	if (num >= tiles.size())
		return tiles[tiles.size()-1];

	return tiles[num];
}
example please!

I'm glad we're being polite again.

Code: Select all


// tManager is a loaded TileManager
// pDriver is a pointer to the Video driver

cTileMapNode n;

sTile t(0,0);
sTile t2(0,1);

n.addTile(t);
n.addTile(t2);




//and later in your drawing routine

n.draw(pDriver, &tManager, 0,0);

Thats it! Of coure nodes by themselves arn't very useful unless they are put together into maps. Which is why in the next section we are going to learn about maps. IM NOT LYING THIS TIME I PROMISE.
Last edited by dmbush on Tue Aug 19, 2008 12:31 am, edited 1 time in total.
dmbush
Posts: 6
Joined: Fri Jul 25, 2008 11:46 pm

Post by dmbush »

Ok, so remember how we said in the last part how maps are basically just two dimensional arrays of map nodes? Well I wasn't lying about that either. I actually use vectors instead of arrays because I love vectors. Not going to fool around this time. Heres the code.

cTileMap.h

Code: Select all

/*
8/11/08
cTileMap.h
Dan Bush
 
	unsigned short nWidth;
	unsigned short nHeight;
};

class cTileMap
{
private:
	ITexture * map;
	TileManager manager;
	vector<vector<cTileMapNode>> nodes;

	unsigned short maxNodeX;
	unsigned short maxNodeY;
	unsigned short nodeWidth;
	unsigned short nodeHeight;

	unsigned short cameraX;
	unsigned short cameraY;
	unsigned short viewPortWidth;
	unsigned short viewPortHeight;

public:
	cTileMap();
	~cTileMap();

	bool freeNodes();

	bool draw(IVideoDriver * driver, const short drawX, const short drawY);

	bool load(IVideoDriver * driver, const std::string s);
	bool save(const std::string s);

	bool setNodeDimensions(const unsigned short w,const  unsigned short h);
	
	bool createNodes(const unsigned short x, const unsigned y);

	
	bool setViewPortDimensions(const unsigned short w, const unsigned short h);
	bool setCamera(const short x, const short y);


	unsigned short getCameraX();
	unsigned short getCameraY();
	TileManager * getTileManager();
	vector<vector<cTileMapNode>> * getNodes();

	bool addTile(sTile t, unsigned int nodeX, unsigned int nodeY);
};



#endif

and cTileMap.cpp

Code: Select all

/*
8/11/08
cTileMap.h
Dan Bush
*/

#include "cTileMap.h"
#include <fstream>
using namespace irr;
using namespace video;
using namespace std;
using namespace core;


//cTileMap()
//initializes everything to arbitrary values
//you probably want to set your own values using setNodeDimensions()... etc..
cTileMap::cTileMap()
{
	map = NULL;
	freeNodes();
	setNodeDimensions(32,32);
	setViewPortDimensions(320,320);
	maxNodeX = 0;
	maxNodeY = 0;
	cameraX = 0;
	cameraY = 0;
}

//~cTileMap()
//calls freeNodes()
cTileMap::~cTileMap()
{
	freeNodes();
}

//freeNodes
//not much to say about this
bool cTileMap::freeNodes()
{
	nodes.clear();
	return true;
}


//draw()
//draws the map to the screen at given positions using the camera viewport
bool cTileMap::draw(IVideoDriver * driver, const short drawX, const short drawY)
{
	//translate from fine coordinates to tile coordinates
	unsigned int startY = cameraY / nodeHeight;
	unsigned int endY = (cameraY + viewPortWidth) / nodeHeight;

	unsigned int startX = cameraX / nodeWidth;
	unsigned int endX = (cameraX + viewPortHeight) / nodeWidth;

	//shift the tiles over because we want to have per pixel scrolling
	unsigned int shiftY = cameraY % nodeHeight;
	unsigned int shiftX = cameraX % nodeWidth;

	//finally draw the tiles using the above calculations
	for(unsigned int y = 0; y <= (endY - startY); y++)
	{
		for(unsigned int x = 0; x <= (endX - startX); x++)
		{
			if((startX + x) > nodes.size()- 1)	//this shouldnt happen ever due to our camera, but just in case
				break;
			if((startY + y) > nodes[startX + x].size() - 1)//this shouldnt happen ever due to our camera, but just in case
				break;

			nodes[startX + x][startY + y].draw(driver, &manager, drawX + ((x * nodeWidth ) - shiftX), drawY + ((y * nodeHeight) - shiftY), &rect<s32>(drawX,drawY, drawX + viewPortWidth, drawY + viewPortHeight));
		}
	}
	return true;
}

//load()
//not yet implemented
bool cTileMap::load(IVideoDriver * driver, const std::string s)
{
	freeNodes();
	ifstream f(s.c_str(), ios::binary);


	//read the header information
	sMapHeader h;
	f.read((char *) &h, sizeof(h));
	createNodes(h.width, h.height);
	setNodeDimensions(h.nWidth, h.nHeight);

	// READ THE TILEMANAGER INFORMATION
	short sets;
	f.read((char *) &sets, sizeof(short));

	for(int q = 0; q < sets; q++)
	{
		char name[30];
		f.read(name, sizeof(char) * 30);
		unsigned short w;
		f.read((char *)&w, sizeof(unsigned short));
		unsigned short h;
		f.read((char *)&h, sizeof(unsigned short));
		unsigned short col;
		f.read((char *)&col, sizeof(unsigned short));

		manager.load(driver, name, w, h, col, SColor((255,255,0,255)));

	}

	for(int y = 0; y < h.height; y++)
	{
		for(int x = 0; x < h.width; x++)
		{
			unsigned char c;
			unsigned short num;
			sTile t(0,0);
			f.read((char *) &c, sizeof(unsigned char));
			f.read((char *) &num, sizeof(unsigned short));
			for(int i = 0; i < num; i++)
			{
				f.read((char *) &t, sizeof(sTile));
				addTile(t, x, y);
			}
		}
	}
	
	f.close();


	return true;
}

//save()
//not yet implemented
bool cTileMap::save(const std::string s)
{
	ofstream f(s.c_str(), ios::binary | ios::trunc);


	// SAVE THE MAP HEADER
	sMapHeader h;
	h.width = maxNodeX;
	h.height = maxNodeY;
	h.nWidth = nodeWidth;
	h.nHeight = nodeHeight;

	f.write((char *) &h, sizeof(h));


	// SAVE THE TILEMANAGER INFORMATION
	short sets = manager.getNumTileSets();
	f.write((char *) &sets, sizeof(short));

	for(int q = 0; q < sets; q++)
	{
		sTileSet t = manager.getTileSetAt(q);
		int s = sizeof(t.texture->getName().c_str());
		char name[30];
		strcpy_s(name, t.texture->getName().c_str());
		f.write(name, sizeof(char)*30);
		f.write((char *)&t.width, sizeof(unsigned short));
		f.write((char *)&t.height, sizeof(unsigned short));
		f.write((char *)&t.collumns, sizeof(unsigned short));
	}


	// SAVE THE NODES

	for(int y = 0; y < h.height; y++)
	{
		for(int x = 0; x < h.width; x++)
		{
			unsigned char c = nodes[x][y].getFlags();
			unsigned short num = nodes[x][y].getNumTiles();
			f.write((char *) &c, sizeof(unsigned char));
			f.write((char *) &num, sizeof(unsigned short));
			for(int i = 0; i < num; i++)
				f.write((char *) &nodes[x][y].getTileAt(i), sizeof(sTile));
		}
	}
	
	f.close();


	return true;
}

//setNodeDimensions()
//self explanatory
bool cTileMap::setNodeDimensions(const unsigned short w, const  unsigned short h)
{
	if(w ==0 || h == 0)
		return false;

	nodeWidth = w;
	nodeHeight = h;
	return true;
}

//createNodes()
//self explanatory
bool cTileMap::createNodes(const unsigned short x, const unsigned y)
{
	freeNodes();
	vector<cTileMapNode> v(y);

	for(int i = 0; i < x; i++)
		nodes.push_back(v);

	maxNodeX = x;
	maxNodeY = y;

	return true;
}


//setViewPortDimensions()
//self explanatory
bool cTileMap::setViewPortDimensions(const unsigned short w, const unsigned short h)
{
	if(w <= 0 || h <= 0)
		return false;

	viewPortWidth = w;
	viewPortHeight = h;
	return true;
}



//getNodes()
//self explanatory
vector<vector<cTileMapNode>> * cTileMap::getNodes()
{
	return &nodes;
}

//getNodes()
//self explanatory
TileManager * cTileMap::getTileManager()
{
	return &manager;
}

//addTile()
//adds a tile to node[x][y]
bool cTileMap::addTile(sTile t, unsigned int nodeX, unsigned int nodeY)
{
	if(nodeX > nodes.size())
		return false;

	if(nodeY > nodes[nodeX].size())
		return false;
	nodes[nodeX][nodeY].addTile(t);

	return true;
}


//setCamera()
//positions the camera upper left coordinate at x,y, this location can be
//outside the map, the camera will automatically shift over to the map boundary
bool cTileMap::setCamera(const short x, const short y)
{
	if (x < 0)
		cameraX = 0;
	else if(x > ((maxNodeX * nodeWidth) - viewPortWidth))
		cameraX = ((maxNodeX * nodeWidth) - viewPortWidth);
	else
		cameraX = x; 

	if (y < 0)
		cameraY = 0;
	else if(y > ((maxNodeY * nodeHeight) - viewPortHeight))
		cameraY = ((maxNodeY * nodeHeight) - viewPortHeight);
	else
		cameraY = y; 

	return true;
}

//getCameraX()
//self explanatory
unsigned short cTileMap::getCameraX()
{
	return cameraX;
}

//getCameraY()
//self explanatory
unsigned short cTileMap::getCameraY()
{
	return cameraY;
}

and a quick example of how to use this:

Code: Select all


		//this will create a map from scratch
      cTileMap map;
      map.createNodes(16,16);
      map.setNodeDimensions(64,64);
      map.setViewPortDimensions(200,200);

		map.getTileManager()->load(pDriver, "tiles.png", 64, 64, 16, SColor(255,255,0,255));

		int i = 0;
		for(int y = 0; y < 16; y++)
		{
			for(int x = 0; x < 16; x++)
			{	
				map.addTile(sTile(0,i), x, y);
				i++;

			}
		}

                //save this for later
		map.save("map.txt");


              //later in your drawing routine
              map.draw(pDriver, 0, 0);



ideally you don't want to create a map from scratch in your code. You would write an editor program to make the maps and save them. If the map is already saved you only have to do this.

Code: Select all

cTileMap map;
map.load(pDriver, "map.txt");
map.setViewPortDimensions(200,200);

//later in your drawing routine
map.draw(pDriver, 0, 0);

pretty slick eh? :)



Hope you enjoyed, comments?
Magus_Stragus
Posts: 86
Joined: Wed Aug 29, 2007 10:45 pm

Post by Magus_Stragus »

Just... AWESOME. I was looking for something like that for my game. I'll try it and I tell you how it went.
Proud member of the Online Campaign for Real English. If you believe in capital letters, correct spelling and good sentence structure then copy this into your signature.
matticusrex
Posts: 6
Joined: Tue Apr 29, 2008 6:45 pm

Post by matticusrex »

I recently implemented this tile map class in a game I'm working on, but I had a snag when trying to access nodes from the map.

When calling the "get nodes" class in cTileMap

Code: Select all

vector<vector<cTileMapNode>> * getNodes();
how do you receive this pointer in another function? I tried multiple things, such as

Code: Select all

vector<vector <cTileMapNode> >* nodes = map.getNodes();
The above code returns a pointer to a vector array with 3 dimensions (eg, I have to call nodes[x][y][z]->blah();)

and

Code: Select all

and vector<cTileMapNode>* nodes = map.getNodes();;
plus all sorts of others combinations, but I get a compile error with everything I try (other than the first option). I'll post the exact code and error when I get home this afternoon. Thanks a ton for any help.
Dorth
Posts: 931
Joined: Sat May 26, 2007 11:03 pm

Post by Dorth »

vector<vector<cTileMapNode>> * getNodes();
tried

Code: Select all

vector<vector<cTileMapNode> > * getNodes();
?

Also, do yourself a favor and pass references around ;)
matticusrex
Posts: 6
Joined: Tue Apr 29, 2008 6:45 pm

Post by matticusrex »

Ok, so here's what I've got right now:

Code: Select all

TileManager tileManager;
TileMapNode node;
TileMap tMap;

.....

void beginGame(IrrlichtDevice* device){
	IVideoDriver* driver = device->getVideoDriver();
	device->setWindowCaption(L"2D Graphics");

	tileManager.load(driver, "Road.bmp", 32, 32, 10, SColor(255,255,0,255));
	loadMap(driver);


		while(device->run() && driver)
		{
			driver->beginScene(true, true, SColor(0,120,102,136));

			vector<vector<TileMapNode> > *mapnodes;
			mapnodes = tMap.getNodes();
			TileMapNode currentNode = mapnodes[0][0];
     }
}

void loadMap(IVideoDriver *driver){

      tMap.createNodes(300,300);
      tMap.setNodeDimensions(32,32);
      tMap.setViewPortDimensions(600,600); 

	  tMap.getTileManager()->load(driver, "road.bmp", 32, 32, 16, SColor(255,255,0,255));

	  int i = 0;
      for(int y = 0; y < 300; y++)
      {
         for(int x = 0; x < 300; x++)
         {   
            tMap.addTile(Tile(0,i), x, y);
            i++;
			if (i > 18){
				i=0;
			}
         }
      } 

}
This code doesn't compile, visual C++ gives this error:
1>armoredfightingvehicles.cpp(137) : error C2440: 'initializing' : cannot convert from 'std::vector<_Ty>' to 'TileMapNode'
1> with
1> [
1> _Ty=TileMapNode
1> ]
1> No constructor could take the source type, or constructor overload resolution was ambiguous
which is pointing to this line:

Code: Select all

TileMapNode currentNode = mapnodes[0][0];
Changing said line to:

Code: Select all

TileMapNode currentNode = mapnodes[0][0][0];
and it compiles just fine, but it's not a 3D array, it's only passing a 2D array. Am I using the vector container wrong? If I try to operate on mapnodes[0][0], the compiler gives a selector box that has functions relevant to a vector, not a TileMapNode.

Halp!
Dorth
Posts: 931
Joined: Sat May 26, 2007 11:03 pm

Post by Dorth »

Well, ofc, you return a pointer to a vector, not a vector (again, use references!)

Anyway, just put ... = *... instead of ... = ...
you might need parenthesis ... = *(...)

Anyway, read up on your pointer syntax and please come to c++ ...
Botanic
Posts: 1
Joined: Tue Mar 10, 2009 9:03 am

Post by Botanic »

nice peace of code. i guess the top of cTileMap.h should look like this and u messed up copy&paste :

/*
8/11/08
cTileMap.h
Dan Bush
*/
#ifndef TILE_MAP_H
#define TILE_MAP_H

using namespace std;

struct sMapHeader {

unsigned short width;
unsigned short height;
unsigned short nWidth;
unsigned short nHeight;
};

class cTileMap
{
private:
ITexture * map;
TileManager manager;

...
And Then Nothing Turned Itself Inside-Out
Ulf
Posts: 281
Joined: Mon Jun 15, 2009 8:53 am
Location: Australia

Post by Ulf »

This is an old post, but I'll post anyway.

I'm about to write my own tile manager type class for scrolling 2D backgrounds.

Just looking quickly over the code shown here...

It seems a bit messed up or backwards.
As an example, why would the tile node need to know about the tile manager class but not the other way around.
I haven't looked at it carefully yet, but it seems backwards.
That alone would make me restructure my class.

Actually, when I look at the last posts that dmbush made, the classes are changed, and the manager class does use the node class.

If someone wants to post this much code, they should upload a working version in a zip file. I don't want to (and I won't) waste my time making someones "idea" work.
I can hear birds chirping
:twisted:

I live in the Eye of Insanity.
tinhtoitrangtay
Posts: 70
Joined: Tue Oct 28, 2008 12:59 pm

Post by tinhtoitrangtay »

Code great release Demo Thanks
milkshakeman
Posts: 27
Joined: Mon Aug 14, 2006 12:36 pm

Post by milkshakeman »

Ulf wrote:It seems a bit messed up or backwards.
As an example, why would the tile node need to know about the tile manager class but not the other way around.
Yes, that sort of awkward class relationships can become real pain in the ass when the game becomes more complex.
I would recommend drawing a few class diagrams before writing any line of code.
Class diagrams with relations between them like this:
http://www.cc.gatech.edu/classes/AY2004 ... agram3.jpg

In my game I also have "manager" classes, and most of the time these classes use "data" classes (IMHO CTilenode is a data class), that do not know about their manager, and mostly just contain data and have a few set/get functions. You can not ask a Tilenode to draw itself.
A manager needs to get the relevant data for drawing out of the tilenode, and call another function to draw this node using this data.
I also like to keep the irrlicht graphics stuff like ITexture* pointers and IVideoDriver* functions in AS FEW classes as possible, not having them running through all your code.
So I would create a new class eg. CTileRenderer.

A quick idea:

Code: Select all

CMap
   / \___________________
  /                      \                       
...                  CTileManager
                      ____ /\________
                    /                \
   tileNode(0..x)* CTilenode     tileRenderer* CTileRenderer
                   |
       tile(0..x)* CTile


CTileRenderer
- holds the pointers to the textures (tilesheets)
- holds the tilesheet metadata (for each tile_ID which tilesheet it is on, and on what position)
- has a draw procedure with parameters a tile_ID and a destination point (where to draw it on the screen), this calls the draw function of irrlicht
CTileManager
- holds the pointers to the TileRenderer and TileNodes
- has a draw procedure (called by CMap::draw) with as input parameter the maptoview offset. The procedure loops trough the tilenodes, gets the IDs of the tiles and their position on the map, calculates where it should be on the screen and calls the TileRenderer to draw it.
CTilenode
- holds the pointers to it's tiles
- holds it's position on the map
- holds other game-related information about this node (for example whether it is visible to the player or not)
CTile
- holds it's tile_ID
Ulf
Posts: 281
Joined: Mon Jun 15, 2009 8:53 am
Location: Australia

Post by Ulf »

Ok I will look it over.
Nice class diagram, is that yours? ... No, a link to a uni course..?
I can hear birds chirping
:twisted:

I live in the Eye of Insanity.
Post Reply