Like almost everything else these days, Icefall is designed around object-oriented programming (OOP) principles. Specifically, Icefall uses Classes instead of objects, although the difference is not relevant for this discussion.

At any given time, the game will be operating dozens of different classes and hundreds of instances of those classes, with the majority of them representing either individual game objects (an item, a monster, the player) or a piece of the user interface (a button, an animation, etc). As you’d expect, many of these classes inherit from other classes (e.g. the button class is inherited from the generic UI class) but the question of exactly how much inheritance to use is something that confuses many people – and if you get it wrong, your code will suffer. So let’s take a closer look.

Inheritance vs Composition

Turning a program’s logic into a group of classes seems easy (hmm, my game has items, let’s make a TItem class!), but choosing the right combination of classes is harder than it looks. Like everything else, the more complex your application is, the harder and more important these choices will be.

One common choice is to do everything via inheritance. You make some ancestor classes which contain only the logic that will be common to all descendents, and you create inherited classes to cover all exceptions or additions to that logic. This style is called inheritance for obvious reasons, and lots of newcomers to OOP start like this. Let’s create an example that shows this in action:

Icefall needs to know an item’s worth (in gold) to a shopkeeper to allow the player to buy/sell it. As items in Icefall can have randomly generated properties, we can’t define the worth of everything at compile time (and even if we could, there will be hundreds of different items in Icefall. Don’t specify things you could calculate. I’ll talk about this another time). So we need a method in the TItem class to calculate the item’s worth for us.


The inheritance solution might look like like this:

TItem = class
   function Worth: Integer; virtual;

function TItem.Worth: Integer;
   result := ILevel * 20; // ILevel is another property.

Nice and easy so far. The item’s worth is calculated as being 20 times it’s ILevel (item-level). But what happens when we want to make some items (let’s say weapons) more valuable than others? The inheritance solution is to build on the existing code and do this:

TWeapon = class (TItem)
   function Worth: Integer; override;

function TWeapon.Worth: Integer;
   result := 100 + ILevel * 50; // Weapons are worth more

We have overridden TItem‘s Worth function with a new one just for TWeapon. We just have to remember that when we’re creating items, if it’s a weapon we need to construct a TWeapon class instead of a TItem class to hold it, and the right Worth function will be called automatically.


Meanwhile, another style, often referred to as composition, says to group similar functionality together, and use members or properties to determine the appropriate behaviour for each instance:


TItem = class
   function Worth: Integer;
   property ItemType: TItemType;

function TItem.Worth: Integer;
   case ItemType of
         result := 100 + ILevel * 50;
      result := ILevel * 20;

Already, for our tiny example, the two implementations are looking considerably different. The composited version defines a class property called ItemType that stores what type of item this instance represents, and the Worth function contains all the functionality for both item types (I used a case statement instead of a simple if, because I suspect I will end up with more than two item types).

p.s. Icefall, of course, takes much more into account to determine an item’s worth: it’s rarity, any enchantments, whether the shopkeeper likes the player, etc etc. But that wasn’t relevant for this discussion 😀


So which is better? What are the pros and cons of each style? Tune in next time to find out!

No Comment.

Add Your Comment