Alternatives to inheritance when creating game classes

jwatte's picture

There is inheritance, there is interface, and there is configuration. Inheritance (of a base class) is seldom the best or most efficient choice. Sometimes, newbies read the wrong tutorials, and start down the rabbit hole of class inheritance, but in the end, that turns out to be a dead end.

Interfaces are much more useful. They are a way of capturing common behavior between different classes, and treat the classes the same. For example, if you have a "settings file" and a "game state" object, and they both have the following three operations:

  • get a list of all value names
  • set a value by name
  • get a value by name

Then you could capture that interface using a simple interface:

public interface INamedValues { 
  IEnumerable<string> Names { get; } 
  bool SetValue(string name, object val); 
  bool GetValue(string name, out object val); 
} 

Now, you can implement your SettingsFile and GameState objects, and make them each implement this interface. After you've done that, you can write functions that take an INamedValues object, rather than a SettingsFile or GameState object, and performs operations on that interface. That way, you don't have to write your code specially for each known class.

When it comes to entities, you could have interface for stats:

public interface IStats { 
  public float INT { get; set; } 
  public float DEX { get; set; } 
  public float STR { get; set; } 
} 

Now, you can have a class Player and a class Monster which each implements IStats. Whenever you need to do something to a thing with stats, you can pass in the IStats interface. You might have interfaces like ICanTakeDamage and IHasTransform and ISpellTarget, too.

Finally, there is configuration. In general, if you want two monsters, where one is strong and brave, and one is weak and chicken, you don't want to implement StrongBraveMonster and WeakChickenMonster. Instead, you just implement Monster, and make properties for strength and braveness. Then, to create a strong, brave monster, you simply create a Monster and configure it appropriately. The nice thing about this is that you can then create a weak, brave monster, or a strong, chicken monster at no extra cost. Typically, you'll take this an additional level, and make a data configuration layer which can create monsters by named templates ("small orc" "medium merman" etc), where the named templates are tweaked by the game designer in something like a text file, or an XML file.

So, tieing back to the beginning, to configure your monsters, they would probably inherit INamedValues to set the various properties (which may include stats and other configuration options), and you'd also implement INamedValues for XmlNode. To create a Monster defined in XML, then, it would look something like:

  XmlDocument templates; 
  templates = new XmlDocument(); 
  templates.Load("Content/templates.xml"); 
 
  Monster MakeMonsterByName(string name) { 
    XmlNode n = templates.SelectSingleNode("//Monster[@id='" + name + "'"); 
    Monster ret = new Monster(); 
    ApplyValues(n.SelectNodes("Value"), ret); 
    return ret; 
  } 
 
  void ApplyValues(XmlNodeList xnl, INamedValues nv) { 
    foreach (XmlNode xn in xnl) { 
      nv.SetValue(xn.Attributes["name"], xn.Attributes["value"]); 
    } 
  } 

The XML code for defining your monsters then looks like:

<Monsters> 
  <Monster id="small orc"> 
    <Value name="STR" value="5"/> 
    <Value name="Bravery" value="0"/> 
  </Monster> 
  <Monster id="big giant"> 
    <Value name="STR" value="20"/> 
    <Value name="Bravery" value="10"/> 
  </Monster> 
</Monsters>