Advertisement

Data Structure for Maps?

Started by April 10, 2000 01:54 PM
17 comments, last by nicba 24 years, 8 months ago
Oh yeah...One more thing...

I use a seperate layer to indicate tiletypes..
A layer that will not be drawn...
100% green tile for grass, brown for dirt etc...

This way i can have terrain like grass, stone, whatever.
It''s a bit easier to keep track of 10 terrain-types than
100´s of normal gfx-tiles.
-------------Ban KalvinB !
quote: Original post by chippydip

You might want to consider a more flexible method for storing the tile indexes in a file.
...
It might look something like this:

0 "grass.bmp"
1 "stone.bmp"
2 "forest.bmp"

... etc ...


I actually (independently of this thread) decided to go with this method a couple of days ago. I don't want to hardcode in either the filenames or the order of my tiles, especially since they are going to be changing throughout the course of my project. I also want it to be trivial for end users to be able to supply their own tilesets for their own maps. It just requires a little more effort on the part of the level editing program, which now has to compile a list of 'used tiles' and generate the array from that, which is a little extra work but brings all the flexibility you mentioned.

As for my representation of my map...

class TerrainType
{
Graphic* theTile; // What to display for MapSquares using this terraintype
// Impassibility and line of sight info
};

class MapSquare
{
TerrainType* itsType; // What kind of terrain do we have here
int x, y; // Our position, sometimes helpful to have it stored here
// Also - Fog of War, ownership (whose territory)
// List of creatures here, list of items here, etc
};

class Level
{
MapSquare* [MAP_WIDTH][MAP_HEIGHT] map;
}

Member functions left out for clarity.

Note that the Level class exposes the main interface for the rest of the program, which gets info from MapSquare, which often gets it from TerrainType. So, instead of:
bool can_walk_here = theLevel.map[x][y]->itsType->CanWalkHere();
I can use:
bool can_walk_here = theLevel.CanWalkHere(x, y);

This means my Level class is a little more tedious to write (effectively duplicating functions already in the other 2 classes), but the overall code is cleaner, and I can change the implementation of both MapSquare and TerrainType behind the scenes, or even remove them and replace with other classes, without breaking AI or Pathing code. I'm sure this is a formalised Design Pattern, but I have no idea which

Hope that helps someone...

Edited by - Kylotan on 4/13/00 4:36:42 AM
Advertisement
quote: Original post by granat

Oh yeah...One more thing...

I use a seperate layer to indicate tiletypes..
A layer that will not be drawn...
100% green tile for grass, brown for dirt etc...

This way i can have terrain like grass, stone, whatever.
It''s a bit easier to keep track of 10 terrain-types than
100´s of normal gfx-tiles.


Storing a colour with each tile is a good idea for 2 reasons. Firstly, you have something to draw even before you have the graphics all done. Secondly, if you ever want a mini-map, just drawing this colour as 1 pixel on the map should suffice.
quote: Original post by Kylotan

class TerrainType
{
Graphic* theTile; // What to display for MapSquares using this terraintype
// Impassibility and line of sight info
};

class MapSquare
{
TerrainType* itsType; // What kind of terrain do we have here
int x, y; // Our position, sometimes helpful to have it stored here
// Also - Fog of War, ownership (whose territory)
// List of creatures here, list of items here, etc
};

Edited by - Kylotan on 4/13/00 4:36:42 AM


This was also more or less what I was thinking of. But if you place your Impassibility and line of sight info in the TerrainType, don''t you get a problem with more ''dynamic'' tiles such as doors (open: passable, closed:impasable)? And what if the tile is occupied by another creature? Then it need to modify its impassibility dosn''t it?

Regards

nicba
quote: Original post by nicba
But if you place your Impassibility and line of sight info in the TerrainType, don''t you get a problem with more ''dynamic'' tiles such as doors (open: passable, closed:impasable)?


Not necessarily. This is where the beauty of object oriented design and encapsulation come in.

bool MapSquare::GetPassability() const
{
if (HasDoor() && DoorClosed())
return false;
else
return itsType->GetPassability();
}

No changes to any member variables were needed, just a few added checks. And again, this can be done without the AI ever needing to know anything has changed behind the scenes - it still just calls Level::GetPassability(x,y).

quote:
And what if the tile is occupied by another creature? Then it need to modify its impassibility dosn''t it?


You never need to modify anything if you use member functions. Then you can only ''fall back'' on the standard value if there are no other weird circumstances. Another example:

int Creature::GetAttackSkill() const
{
// Special cases
if (asleep // unconscious)
return 0;
if (blind)
return itsType->GetAttackSkill() / 3;
if (berserk)
return itsType->GetAttackSkill() * 2;
// Default
return itsType->GetAttackSkill();
}

The basic AttackSkill still only needs to be implemented in the CreatureType class as it is the same for all Creatures like that.

As it happens, I am intending implementing doors as objects that sit on a mapsquare, rather than part of the mapsquare. the movement algorithm for a creature first checks if there is an enemy creature there, then if there''s a blocking object, and finally the call to mapsquare impassibility is made (which obviously doesn''t include creature or object blockages in my engine.)
quote: Original post by Kylotan

bool MapSquare::GetPassability() const
{
if (HasDoor() && DoorClosed())
return false;
else
return itsType->GetPassability();
}

...

int Creature::GetAttackSkill() const
{
// Special cases
if (asleep // unconscious)
return 0;
if (blind)
return itsType->GetAttackSkill() / 3;
if (berserk)
return itsType->GetAttackSkill() * 2;
// Default
return itsType->GetAttackSkill();
}



Beatiful, absolutely beatiful!

Why is that everyone has to discover these thing for himself/herself? I mean, theres lot of tutorials on making 3D graphics, AI and such things, but I never seen a tutorial describing the use of good object oriented design as discussed in this thread.

Thanks for your help.

nicba
Advertisement
Hi

Kylotan what do you do about the properties of your terrain types (impassable, transparent and so on)?

Do you hard-code the different properties into your engine and tile editor and then set up a row of checkboxes in the editor to set/unset the properties? Or do you allow for the user of the editor to input any properties in the form of pairs of property names and values?

I could see an advantage of just loading the tile editor and prees ''add new property'' whenever I found out that I needed another crazy property (ex. if I needed to make some tiles ''slipery''). But on the other hand I must still edit the engine to support the new property so perhaps the advantage would be quite small after all?

Regards

nicba

I had a slightly longer reply to this but Windows crashed, so I'll try and be brief but still say everything...

Firstly Nicba, thanks for the kind words. I am fairly happy with my design, and although it is not perfect, it is doing what I want, and that is the main thing And if sharing it helps someone else, I am glad too.

I think that perhaps the reason there is little in the way of object-oriented design (whether minimal, like my encapsulation approach, or more extensive, like using a deep hierarchy of classes) is because C++ only came into popularity in game development once 3D acceleration was already on the scene, and many advanced programmers moved on to that side of things. Which is a shame, as I feel there is still a lot to be said for 2D, and there's no reason the 3D hardware can't be used to enhance it anyway.

Properties... yes, I hardcode them in this engine. I have little reason to be able to add them dynamically as the only way you would be able to check a user-added property is for the user to ask for it. As you said, the engine itself would never know the new property existed unless you amended it. This probably means implementing some sort of scripting in the game so that level designers can access properties they create themselves without the engine needing to know. Which in my case, is complexity I don't really need. I figured I could get enough versatility for my game in a preset number of properties, providing I thought them through beforehand. (Note - I store my boolean properties, or flags, as individual bits within a single int - this means that my file format can stay the same until I exceed 32 flags per mapsquare. Which is unlikely since I never seem to need more than 6 flags anyway.)

Of course, if I wanted such a system (as I have in a totally different game I am making), I would do it like this:

struct Property
{
Property(String n, int v) : name(n), value(v) {}; // Basic constructor
String name;
int value;
};

void Entity::AddProperty(String name, int value)
{
propertylist.add(Property(name, value));
}

int Entity::GetProperty(String name)
{
for (each property in list)
{
if (property->name == name)
return property->value;
}
// Not found - return 0 (or any other default you choose)
return 0;
}

void Entity::SetProperty(String name, int newvalue)
{
for (each property in list)
{
if (property->name == name)
{
property->value = newvalue;
return;
}
return;
}

How you implement the lists may depend on how many properties you expect to need to support. I might use an STL map to store them if I had more than 10 or so per entity, for a quicker lookup time. There will be better ways of doing it than I did above: that is just for illustration purposes. Again, you can change the internal representation of the properties to whatever you like, as the rest of the game only needs to know the AddProperty, GetProperty, and SetProperty function calls. (Note: you could technically combine AddProperty and SetProperty, but I didn't here, for clarity.)

But again, I stress that all the above has not been found necessary in my tilebased game so far as there seem to be so few cases (ie. none) where I need the user to be able to set some 'custom' property. Your situation may vary, though.

Happy to help.

Edited by - Kylotan on 4/13/00 9:40:17 PM
going back to an earlier comment, another way to allocate and deallocate a map is by creating it on the heap (using the NEW operator).

int *Map;

void CreateMap(int Width, int Height) {
Map = new int[Width][Height];
}

void DeleteMap(void) {
map = delete [] Map;
}

dont quote me on the exact source code, it''s probably wrong.

when using heap memory, you MUST delete it, or else it stays there, taking up useless space (which is why i included the DeleteMap function).

you have been warned (well, bored)

MENTAL

This topic is closed to new replies.

Advertisement