OOP for Roguelikes
When designing a game architecture with an OOP (Object Oriented Programming) approach, one naturaly tend to put the data and the logic associated with a game entity in the same place, namely a class.
So you create classes like Item, Actor, Map and the likes and make a one-to-one relation between a game entity and an instance of a class (object).
One particular Sword entity in the game will likely be an instance of ItemMeleeWeapon, which a hiearchy like ItemMeleeWeapon is ItemWeapon is Item.
Entities Interactions : Assigning responsabilities
Works fine. But then what about entities interaction? Say the player actor wants to open the big door.
Who does it? There are many differents ways and paths to choose. The most common are:
- The Actor does it: playerActor.OpenDoor(theBigDoor), which in turns calls theBigDoor.SetState(OPEN) etc...
- The Map does it: map.ActorOpenDoor(thePlayer, theBigDoor)...
- The Action does it: openDoorAction.Execute()...
Shared responsabilities = Spaghetti Logic = Bugs and Refactoring Hell
The danger -read: the source of bugs or game logic inconsistencies- resides when one entity (active) initiate an action on another entity (passive) which could trigger a response somewhere else (eg: the door electrocutes the actor) which in turn could trigger another response (eg: the actor roll for electrocution resistance) etc.. and you end up with spaghetti logic. In general this ends up provoking bugs, because somewhere in one of your derived classes you made an assumption about the caller or some property that is broken by the logic chain triggered by the action initiator...
People who take this standard OOP approach tend to search for the "perfect" architecture, refactor their code a lot and most of the time aim for a roguelike engine rather than a game. It works but with great pain.
Game Data separated from Game Logic : Multiple Data, One Controller
In Rogue Survivor I choose since the start to go with a Data-Driven approach with a single Controller.
All the game entities are represented by Data classes. Data classes are "stupid" and only know to maintain themselves. Data classes use inheritance only when requiring additional fields and properties. Eg: ItemWeapon derives from Item because it needs additionnal data to represent a weapon.
All the game logic in centralized in one place, the Game. The RogueGame class has absolute control on everything, is the game master and holds all the game logic.
Actions are Data too, whose job is only to call the RogueGame appropriate methods when checking for legality or executing. Remember, as Data classes they have no game logic in them at all. They are like a dumbed down version of the Command design pattern.
Types of entities (eg: the skeleton actor type, the crowbar item type) are instances of Data classes too, called Models (eg: skeleton actor type is an instance of ActorModel).
AIs are special cases because I consider them as abstract game logic, since the AI logic in itself does nothing in the game world, it just computes stuff and produce an Action command.
The Holy Grail?
Certainly not. In other pet game projects I favor other architectures such as the standard OOP mentionned above.
For Rogue Survivor it just works and is very easy to maintain. All architectures have limitations and flaws, you just have to accept them.
End of post.