I started rewriting the entire project yesterday and so far I’m really pleased with the results. I find it helps me to implement something, analyze it, and then scrap it completely. That way I gain the experience of why certain decisions where good and why others were bad.
Some changes I made in the new design are as follows:
- I got rid of the IDungeonTile metaphor. IDungeonTile constrained me to dungeons and contained logic related to movement. I now use an ITerrainType interface which contains one property describing the terrain composition. This change brings about the Terrain metaphor allowing me to use the same construct for towns, wilderness and dungeons. Each ITerrainType instance defines its own composition based on a set of flags for example Acid, Granite, Lava, etc.
- I shifted the focus to from a player to actors. No longer do I have hard coded logic based on the player, but rather handle anything from the perspective of an actor. Monsters, NPCs and players are all actors. For testing purposes I take out the player and just have monsters playing the game.
- Ok, I don’t really have monsters or NPCs either, but rather an actor with an intellect. The intellect is defined by the IIntellect interface and allows me to give an actor a specific IIntellect implementation. Currently I make use of a ComputerAI and PlayerAI. The ComputerAI implementation will make decisions based on it’s needs at the time. Decisions are translated into Commands and then executed by the game engine. PlayerAI is under the control of the player and does nothing until it receives an input command from the player which is then translated into a Command.
- From the above you will realize that I’ve added a generic command structure using the Command design pattern. All actions within the game get translated into a ICommand object and are executed in turn by the engine.
- IMovementBehaviour has become IMovementProfile. I just like the terminology better. I changed the handle bump functionality somewhat. The movement profile now provides a default action to perform when an actor collides with something. This is useful if I have an actor that can dig through walls versus an actor that moves like a humanoid. The default action for a digging actor when colliding with rock would be to dig versus a humanoid actor who would do nothing.
- I added a new IFovProfile to my field of view code. The field of view profile describes how something “sees” in the game. This includes the field of view shape, sight range, and some functionality to determine if something is visible based on the amount of light it receives. The last bit of functionality is useful to implement blindness, infravision, etc.
- I created a GameLevel object that will manage the aspects of the particular game level. This could be a town, wilderness area or a dungeon. The GameLevel has a TerrainMap (which is a bunch of ITerrainType instances), a list of actors, and a lightmap (which describes the lighting information for the level). In the future I will add a list of level objects i.e. treasure to the game level.
The above changes are a big departure from the original design, but feel a lot more like a game design (from a software design perspective) rather than a dungeon generator with stuff hacked onto it. I am thinking that the IMovementProfile and IFovProfile interfaces will probably move from the Actor class to some kind of Race class. That way the actor’s race would define its movement and field of view characteristics.
My new implementation has already raised some more questions. When I check for a collision I ask my GameLevel object for the composition properties of the terrain at the location I want to move to. Then I ask my GameLevel object for the composition properties of any actors at that location. An actor has a collision when the union of the terrain and actor composition properties is impassable for the IMovementProfile at the target location.
The question is whether I should leave the check as two separate calls, one for terrain and one for actors? This seems a bit clumsy. To change it to a single method call would necessitate the ITerrainType and Actor classes to inherit from the same base class. The base class would define a common Composition property which is used during collision checks. As a rule I am not keen on inheritance hierarchies and never imagined all my “game” objects inheriting from some super class.
Hence the need for more hindsight.