Re: 2D Tile Game - Keeping location data between Map & Things synced up (longish)



Responding to M....

O.K. I'll step back a bit. I'm trying to make a tile-based RPGish
game. These are the core things I need to simulate my game world:

[1] A bunch of landscapes/areas, conceptualized as 2D grids. On these
grids there can be various mobile things at different
coordinates/tiles/locations. In there are terrain features such as
with various, arbitrary properties. For example, "walls" have the
property that they completely prevent anything from moving onto them.

OK, as you noted, that strongly argues for some notion of 'tile' that has terrain and Things associated with it.

Whether the associated terrain can be expressed as an attribute (e.g., with values like FOREST, WALL, etc.) or needs a more complex representation depends on how Things interact with terrain. IOW, if terrain is complex enough, it may warrant being abstracted as an object and subclassed. Then [Tile] would just provide a placeholder for gathering both Things and Terrains.

[2] Things that can be put into my landscapes and move around
(depending on the local terrain). Something I'd also like is things
being carried by other things.

The notion of [Tile] allows multiple things to be stacked. It also allows movement to be <simplistically> described by relationship instantiation (more below).

[3] Ways to manipulate the properties and placement of potentially
anything on a map. For one thing, I need an AI to take a creature,
look at the creature's surroundings, figure out what valid actions
the creature can take, and tell the creature to do something. I also
want different entities to manipulate either themselves or things
nearby, either passively or through a "use" method. Lava pools
should burn creatures that walk onto them. "Closed doors" should
be "opened" to turn into "open doors." Similarly, a "magic
potion" when "drunk" should boost the health of the creature
holding it. I want to keep this as wide as I can, and am thinking of
putting scripting into my game in the later phases.

OK, as I understand this the AI (or Player) decides What a Thing should do based on context while the Thing knows How to do it. Thus the AI decides the Thing in hand should move to {x,y} and tells Thing to do so. Then Thing.move() does the relationship instantiation thing to actually do it. That's fine because Thing.move() is just executing the mechanics of shifting position from one <given> grid position to another <given> grid position.

However, I would caution that KISS rules and one needs to be careful about allocating responsibilities. One example is the AI decision making. If that gets complicated, you may need multiple objects just to abstract the AI (e.g., an grid analysis module and a path-finding module).

Another example would be the complexity of the move itself. If the destination is not adjacent, does Thing need to interact with other Things or Terrain along some path? Is the move a compound activity (e.g., opening a door, moving through it to the next grid location, and closing the door)? If the move involves more than simply changing grid locations, the move() responsibility may need to be broken up into more atomic activities and some of those may be more logically abstracted in a different object that collaborates with Thing to actually do the move.

[If you do that, you may name each atomic responsibility differently. Then you might end up with no move() responsibility at all. That was one reason I suggested I wouldn't expect a Thing.move() at all. (The other was that I envision the sort of relationship mechanics that I show below, which the AI might do directly.)]


In terms of what I've described in my first post, Maps are
"collection" classes where Things and terrain can be placed,
observed, and manipulated. Things can be told to move around to
different locations on different Maps as long as the terrain at the
destination lets them. For example, locations with walls on them never
let Things get placed on them. Observation is apparent mostly with AI.

OK, but you will need some sort of game algorithm to "walk" the Map that knows what to do when it encounters grid locations with particular stuff on them. That argues for subdividing Map into distinct Tiles again because that captures the important notions better about how distinct different locations are. IOW, one separates the structure of the Map from how one deals with the stuff on particular Tiles.

In my game an AI is given a creature-type Thing. The AI then gets the
Map its "body" is on to look at. For example, the AI will want to
ask what locations adjacent to its body do not block movement.

You can do that dynamically by manipulating {x,y} coordinates or you can do it through instantiating relationships that describe adjacency statically. Doing it dynamically is usually more complicated and needs to be done every time one needs an adjacent location. Doing it statically is attractive because one only has to do it once when the map is initialized or Things move regardless of how many collaborations there are. But that may be a bit messy if one needs 8 different directions (i.e., a hex grid) so it might be better to play games with lists ordered by coordinate. (The specific implementation clearly depends on how one "walks" the adjacent locations.)

Eventually the AI will tell its body to move to a valid location or do
some other action. Manipulation happens with other types of events.
When a bomb explodes, it gets its Map and from that gets all of the
creatures within range of its coordinates. The bomb will then deal
damage to all of those creatures. Events are why I want Things to know
their locations. Most events are local to the Map and location of
whatever triggered it.

Right. An event-based structure is good for things like this. The Bomb Thing knows it range, "walks" the map, and sends an event to every Thing in range. Each receiver Thing responds by taking damage.

However, the problem here is that getting a list of Things within a given range may be a common activity for other Things than Bomb. In addition, obtaining that list requires a lot of knowledge about how the Map is structured that Bomb really doesn't want to know about. So I would think about making getThingsInRange a responsibility of Map. Bomb then just gets the list from Map and addresses the right event to each member of the list.

An interesting question, given the description of what you do now: How
to you get the coordinates if the Map location is vacant (i.e., there is
no resident Thing)?


The method would return null. I'm not sure if this is a good idea or
not, but I do provide a isOccupied(x, y) method as well.

Again, this argues for a distinct [Tile].

[Map]
| 1
|
| R1
|
| consists of
| *
[Tile]
+ xCoordinate
+ yCoordinate
| 1
| currently located on
|
| R2
|
| 1
[Thing]

<snip>

However, at the design level, my first push back would be: Does Thing
move itself or does someone else move it? If the Player or the AI is
actually determining the move, then the move is really nothing more than
re-instantiating relationships. For example, in the model above, moving
a Thing one one location to another is simply removing the current R2
relationship and reinstantiating it to the new Tile. In that case I
would not expect [Thing] to have a move() property at all.


Yeah, most movement of Things is caused and controlled by AIs and other
events, so you're probably on to something. Actually, I *could* have
made AIs internal to Things (they really would move themselves).
Having a separate AI though let me create just one AI that can control
many actors in turn, and it was easier to modify into part of the
player interface. I was thinking of having most events be defined
inside a Thing's "trigger" method, but these events could
manipulate other Things besides the triggered Thing anyway.

I think the separate AI is better because it allows concerns to be separated and encapsulated. Putting disparate responsibilities in Thing just makes it a god object that hulks in the middle of the application with tentacles everywhere.

I'm not sure I understand your Thing movement with Tiles. One of the
impressions I got is that movement would look something like this:
aThing.exitFromTile();
aThing.enterTile(aMap.getTile(newX, newY));

In C++ (I'm dating myself because that was the last OOPL I learned before going to translation techniques):

class Thing
{
private:
Tile* myCurrentTile; // implements R2 for Thing
...
public:
Thing (Tile*, ...); // instantiates original R2
void move (Tile*);
...
}

class Tile
private:
ThingSet* myThingSet; // implements R2 for Tile
...
public:
ThingSet* getThingSet () {return myThingSet;};
...
}

class ThingSet
{
private:
Thing* (myThings []); // store references (may be ordered)
...
public:
void addThing (Thing*); // instantiates R2 by adding to array
void removeThing (Thing*); // deinstantiates R2
...
}

Thing::move (Tile* newTile)
{
ThingSet* tileThings;

// deinstantiate old R2 on current Tile side
tileThings = myCurrentTile->getThingSet ();
tileThings-> removeThing (this);

// instantiate new R2 on new Tile side
tileThings = newTile->getThingSet ();
tileThings->addThing (this);

// deinstantiate old R2 and instantiate new R2 on Thing side
myCurrentTile = newTile;
}

One thing to note here is that relationships are orthogonal to class semantics. Unfortunately that is not very obvious in OOPLs, especially C++ because of the type systems if one wants the code to be readable for manual code maintenance. However, a full code generator would write quite abstract code where names would change and one would use Object*. Thus

ThingSet* myThingSet;

would be

Object* R2Set;

which the code generator can write directly from the OOA/D Class Model without knowing anything about what a Tile or Thing is.

A more important thing to note is that by proactively using relationships this way one greatly simplifies collaborations. No matter what the nature of the collaboration, each participant can be completely confident it is talking to the right object. Essentially we have captured the rules of the Map structure and the current context of a Thing in static relationships that are independent of individual collaborations. We have also separated the concerns of Map navigation (i.e., getting to the right participant) from the concerns of the collaboration itself (i.e., why the objects are talking to each other).

One way this is manifested is versatility in responding to changing requirements. By isolating the instantiation to a single method, it no longer matters whether Thing moves one location or many locations. So if one decide to walk paths with possible interactions along the way, one just has to move along the path one location at a time using exactly the same logic. IOW, the nature of the nature of movement is defined externally (step-wise path walking vs. instant relocation) to the basic mechanics of instantiating relationships and that mechanics is in no way affected by the change in the way movement is done in the overall game context.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



Relevant Pages

  • RFC: Very fast LOS/FOV
    ... if it blocks sight, it blocks x. Calculate the distance from x to @, and store ... Repeat this for every tile in the grid. ... Now, when you need to update the FOV from a given map square, simply walk the ...
    (rec.games.roguelike.development)
  • Re: Getting view in the document class
    ... SOLELY the responsibility of the view, to map the contents of some x,y coordinate of the ... Given how triivial it is to map an xy coordinate of an object to a rect on the screen, ... Each tile knows where it's located (it has a CRect that describes its ... invalidates itself. ...
    (microsoft.public.vc.mfc)
  • Re: Garmin Geko - Accuracy of conversion to Bristish National Grid
    ... > drawn on the map, ... not give accurate Grid References not because the GPS can not determine its true ... the significance of your use of OziExplorer to calcuate the a Grid Reference ...
    (sci.geo.satellite-nav)
  • Re: World map embedded in userform.
    ... Those are simply edited "workings" to show how to convert grid coordinates ... Peter T ... Hereby the requested co-ordinates might be different than the last, ... coordinates are put into a sheet, a marker of some kind shows on the map ...
    (microsoft.public.excel.programming)
  • Re: World map embedded in userform.
    ... Those are simply edited "workings" to show how to convert grid coordinates ... Peter T ... Hereby the requested co-ordinates might be different than the last, ... coordinates are put into a sheet, a marker of some kind shows on the map ...
    (microsoft.public.excel.programming)