HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMajor

Object-oriented dungeon crawler game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
crawlergameorientedobjectdungeon

Problem

I am creating a simple dungeon crawler game. My program runs and does what I want it to do so far, but I am starting to run into difficulty with adding new functionality, and I think one of the main issues is my class design.

I want to refactor my classes according to proper OOP principles, of which I am attempting to understand. I would like to have a better understanding of this now so that I will have an easier time going forward.

I am thinking about restructuring my classes according to the following:

Character:

  • id



  • name



  • currenthp



  • maximumhp



  • level



  • experience



  • Room (Character should know its location, correct?)



  • Monster (Character should also know its target, correct?)



Monster:

  • id



  • name



  • currenthp



  • maximumhp



  • level



  • Room (like Character, Monster should know its location?)



  • Character (like Character, Monster should know its target?)



Room:

  • id



  • location



  • The location of its doors



  • Dungeon? (should Room know what Dungeon it is in?)



  • Monster(s)? (should Room know what monsters are in it?)



  • Character? (should Roomknow what character is in it?)



Dungeon:

  • id



  • name



  • Rooms



Does the above seem like a reasonable design for my classes? My main question is: to what extent should each class know about the other classes?

Character class:

```
private class Character
{
public string name { get; set; }
public int level { get; set; }
public Tuple currentLocation { get; set; }
public int currentHP { get; set; }
public int maximumHP { get; set; }
public Dungeon dungeon { get; set; }
public Monster target { get; set; }

public Character(string name, Dungeon dungeon)
{
this.name = name;
level = 1;
maximumHP = 100;
currentHP = 100;
this.dungeon = dungeon;

switch (dungeon.Value)
{
case 1: //Dungeon 1
currentLocation = new Tuple(5, 0);

Solution

First off, I wrote a series of articles about some of the problems of class design in this domain, though it really is about class design in general. Wizards and warriors is just the "fun" domain that makes it interesting:

https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/

You should read the whole series and understand all of it; the takeaway is: it is surprisingly hard to come up with good classes in the wizards and warriors domain, so don't feel bad if you are struggling with this. There are many pitfalls.

The other answer has some good advice, and some that is questionable. (There is a small dungeon class and a dark dungeon class, so what class do you use if you want a small, dark dungeon? Don't use the "inheritance pivot" on properties that are orthogonal. Just make a property "size" and a property "light" in the base class.)

Here's more good advice.

Several of your parenthetical questions are about location awareness:


Character should know its location, correct?

The classic way to solve this problem is to make every object that exists in the dungeon inherit from a common base class: GameObject, say. Every GameObject has:

  • A parent, which is another GameObject; all game objects have a parent except the root, which is typically the dungeon. The parent of the key is the bag, the parent of the bag is the player, the parent of the player is the jail cell, the parent of the jail cell is the dungeon, which has no parent.



  • A list, possibly empty, of child GameObjects.



And, most important:

  • We require that the parent-child relationship be consistent! If the parent of the key is the bag, then the key must be a child of the bag.



Moving around the dungeon is now just a special case of the general problem of moving objects. When the dragon moves from the drawbridge to the moat, the dragon stops being a child of the drawbridge and starts being a child of the moat.

Getting these relationships coded correctly is of vital importance; you don't want "put the bag inside the bag" to produce an infinite regress, and you certainly don't want "put the library in the bag" to work. Think of all the ways things can go terribly wrong, and figure out a way to prevent them.

Looking at your class design: I notice that Character and Monster are almost exactly identical. This should tell you something. Characters and Monsters are the same thing. The difference between a character and a monster is that the characters actions are determined by the player, and the monsters actions are determined by an AI. But from the point of view of the game engine, they're the same. So make them the same.

Notice that in your proposed design you have silently baked limitations of the game into the type system. A monster can have only one location; OK, that seems reasonable. That location is a room -- too restrictive. You should be able to put the dragon in a cage, and the cage in a room. A monster can only target characters? Too restrictive; a dragon should be able to attack a werewolf. (Again, we see that characters and monsters are really the same thing.) A monster can only target one opponent? Too restrictive; what if the dragon wants to claw one character and fire breathe on another?

Think about what limitations you are baking in when you design your class hierarchies, and make sure you are only baking in things that really have to be invariants, like having a single location.

Finally, as I note in my series: use good OO design for more than just the game objects. There are lots of coding concepts in a game other than the objects manipulated by the player in the game; there are players and rules and commands and actions and many other concepts. Those are actually the core of your game engine; design them carefully too.

Context

StackExchange Code Review Q#140911, answer score: 22

Revisions (0)

No revisions yet.