On Saturday, March 24, 2012 4:41:12 AM UTC-4, jupiter 999 wrote:
> Good day, everyone,
>
> I had a question that bugs me for quite long time already.
> Say, I had a 2D array to represent my wilderness map.
> What's the pros and cons if:-
> 1) each cell in the 2D array shall store int that represents terrain
> type using enumeration, or, in other words, a 2D array that stores
> terrain ID, or,
> 2) each cell in the 2D array shall store the instance of terrain
> class.
3) It doesn't matter to users of your map class which one of those
it is.
By users, I mean the rest of your code base outside of map.C.
I'm strongly against "Tile Classes" - they are really painful to keep
up to date and result in array of struct implementations rather than
structs of arrays. For any reasonable efficiency, the latter is always
preferable as you can take advantage of different storage algorithms
for different parts of your virtualized tile class.
To pick monsters, for an example. If you store them inside your
tile, you can end up with very expensive searches when you ask for
"All monsters in this area" since you have to iterate over all tiles
in that area, which might be much larger than the total number of
monsters in your game! Likewise, if the monsters live only in the
tile, you have a lot of very careful code to write when you move a
monster for fear of accidentally leaving one orphaned. With a
separate list of monsters that is the "official" monster list, and
the in-tile storage of monsters being but a cache, you can ensure
there are at worse drawing peculiarities with these mistakes rather
than having monsters just disappear.
With this in mind, it is *very* important not to expose these tiles
to anyone outside of your map class. If the monster class can go
modifying tile contents, any caching structure you build in your
map class won't be properly invalidated.
This doesn't mean you can't have all the convenience of something
that looks like a tile class. You just have to realize you never
actually wanted tiles in the first place.
The thing that you should be exposing is a very different class:
Position.
class POS
{
public:
POS delta(int dx, int dy);
TERRAIN_ENUM terrain() const { return map->getTerrain(x, y); }
MONSTER *monster() const { return map->getMonster(x, y); }
private:
int x, y;
MAP *map;
};
This is a flyweight object you can construct and use to refer to
locations on the map, (ie, tiles!) but is *not* a tile itself. It
will have all the functions you want to read and edit tiles: getting
the terrain, monsters, items, etc. But it will know how to pass that
through the map.
With this approach, the exact storage in MAP is quite moot. There
are no syntax advantages to an array of heavy weight tiles, while
there are significant advantages to having multiple arrays of stuff...
class MAP
{
public:
...
private:
GRID<TERRAIN_ENUM> myTerrain;
GRID<MONSTER *> myMonsterCache; // Per square cache
std::vector<MONSTER *> myMonsters; // All monsters
GRID<ITEM *> myItemCache; // Only stores top item!
std::vector<ITEM *> myItems; // All items
GRID<float> myUraniumOre; // Random attribute
};
Of course, you can have your name-value pair dictionary of grids
to store arbitrary run-time attributes. You can also replace a 2d
dense grid with any other structure you like.
Now, what do we do in the monster class?
With tiles-as-objects, people might do
class MONSTER
{
TILE *myTile;
};
which means every tile needs to store it's x,y coordinate! And, if
you want to find the positions next to the tile, you need to store a
back pointer to the map! Ugh!
Instead,
class MONSTER
{
POS myPosition;
}
Note POS is not a pointer - the monster owns this object. (For those
who think Java means they can forget ownership: no, it doesn't, this
is important.)
In summary:
1) It is essential how you implement the guts of your map is not polluting
the rest of your roguelike.
2) The convenience of TILE * only occurs if you expose it, violating 1.
3) You will want a POS class anyways to consolidate (x, y) pairs. And
very quickly after doing that you'll notice you really want (x, y, map).
4) So, make POS your TILE class.
--
Jeff Lait
(POWDER:
http://www.zincland.com/powder)