Configuration - loaded from XML files, has Descriptors, among other things

Atlas - has a Configuration, has a CreatureInstance(represents the player's creature instance), is an associative array of AtlasColumns

AtlasColumn - has an Atlas(parent), is an associative array of AtlasCells

AtlasCell - has an AtlasColumn(parent), is an associative array of Layers

Layer - has an AtlasCell(parent), is an associative array of LayerColumns

LayerColumn - has a Layer(parent), is an associative array of LayerCells

LayerCell - has a LayerColumn(parent), has a CreatureInstance, a array of ItemInstances, and a TerrainInstance

CreatureInstance/ItemInstance/TerrainInstance - has a LayerCell(parent), has a Descriptor

Descriptor - has an associative array of properties needed by an instance to do its particular function

The most important thing in the Configuration are the Descriptors. A Descriptor has all of the properties that are needed by the various types of instances. It has properties that drive behavior, what it looks like on screen, and so on.

So here's the part that bothers me:

Everything... EVERYTHING, from Atlas to TerrainInstance can talk up and down the entire chain, the reason being that if, for example, the TerrainInstance is a square upon which a player steps and suddenly a number of monsters are conjured around him, the TerrainInstance needs to have access to the neighboring LayerCells. Or for another example if a CreatureInstance when attacking the player's CreatureInstance is able to take items from the player's inventory, and relocate them somewhere else in the dungeon, it needs access to a different Layer or AtlasCell in order to put the item where it needs to go.

An alternative that would appear to be "better encapsulated" might be a messaging system that sends little message objects up and down the chain and handles things at the appropriate level, but is that really any BETTER than just having the objects be aware of one another?

The game I'm writing started out with mostly hardcoded item and creature types, and gradually morphed its way into something similar to the more flexible roguelike framework that I've detailed above. I'm finally switching from C# to Java for it(the language is immaterial, however), and I'd like to not have missed something glaringly obvious that will cause great pains later when I need to refactor it.

So, the question: is the apparent lack of encapsulation within the data/business objects of the game demonstrate a fatal flaw in the architecture? And if so, what is that flaw and how can I correct it before I get too far along?

Could you put in a brief description of what Atlases, AtlasColumns, and AtlasCells are and what purpose they serve for folks who haven't built a roguelike before? Everything else intuitively makes sense from the name (assuming Layer is for different floors [ground level, second floor, basement, etc.] of the dungeon?).
–
michael.bartnettFeb 16 '12 at 23:06

2 Answers
2

There's nothing wrong with objects knowing about other objects. That doesn't defy encapsulation.

However, I don't think anything is gained by having CreatureInstance actually store a reference to it's LayerCell. That's a needless coupling of data. It should have a "location" type, and Atlas should have a FindCell function that takes this location type.

Or for another example if a CreatureInstance when attacking the player's CreatureInstance is able to take items from the player's inventory, and relocate them somewhere else in the dungeon, it needs access to a different Layer or AtlasCell in order to put the item where it needs to go.

What you're saying is that the CreatureInstance itself needs to directly do the following:

Detect that it's attack hit the target.

Look in the inventory of the target.

Remove some items form it.

Place those items in the world.

This is highly unnecessary. Why does the CreatureInstance have to do this? Yes, conceptually, it is the creature's attack that causes items to be removed. But why does the CreatureInstance object have to deal with that?

Here's how I would do it.

The CreatureInstance would have one or more Attacks. An Attack is an object that, when successful, can cause one or more effects. Effects include:

Dealing damage, possibly with damage types.

Removing items from the target and placing them on the map.

etc.

All the CreatureInstance has to know is what Attacks it can perform and who it can perform them on. When it decides to use the DrainItemsAttack, it simply says, "I'm using DrainItemsAttack on this target."

The DrainItemsAttack would invoke the "RemoveAndDropItems" effect. That's not a class; that's a free function (or a static member of some class, since you're using languages that don't allow free functions). It doesn't have to have state, so it doesn't need to be a type. It's just a function that takes some number of inventory items from a CreatureInstance and places them on the ground.

It is that function which needs to be able to inspect inventory, remove items, find suitable terrain to place them on, etc. Each of these operations should be its own function. So your "RemoveAndDropItems" effect would be implemented like this (pseudo-code):

This way, CreatureInstance isn't responsible for finding places to put stuff. You have functions for that. Nor does it have to have knowledge of ItemInstance or other types; that's all taken care of elsewhere.

actually, much of what you describe is already what takes place.... the creatureinstance itself doesn't do much of anything. it just stores some state information. the descriptor ATTACHED to creatureinstance has a property which gives an interface that has a function that handles the "event" of attacking(technically a "bump" event between two creatures), and only the function in that property spans the whole tree of atlas to creatureinstance.
–
PlayDeezGamesFeb 16 '12 at 21:00

I've been designing my roguelike framework on and off for the past few years and I come to think that the traditional OOP approach doesn't provide a very useful abstraction for a typical roguelike world for the reasons that you mentioned - everything is so interdependent on each other that each class may potentially need to know about every other class.

For example, AI routines should logically be a part of the Creature class, but AI potentially needs to know everything - the current dungeon map, the state of PC's quests, current World events etc. Even implementing simplest actions such as moving isn't straightforward: do you have a Creature.move(x,y) method and require every Creature object to know its current dungeon level, OR you have a DungeonLevel.moveCreature(creature, x, y) method? What if monsters in your game can move from one dungeon level to another - do you need an even higher level method, e.g. World.moveCreature(creature, dungeonLevel, x, y) ?

So in the end I decided to simply make most functions global, instead of agonizing over the correct location of each method in the class hierarchy, and use classes mostly as data structures. Now my Creature object doesn't have to link back to the parent object, and it contains only simplest code that doesn't need to be aware of other classes.