Titouan Deslandes

Gameplay programmer

Item decorators

This article is part of my internship report < click.

Introduction

Say you have a game in which characters will be able to hold items in their inventory and eventually wield them. These items could be artifacts you found, unknown stuff you want to analyze, weapons, tools, etc... we need a way to represent all the possible variations and what they imply.

To get things started we simply created an Inventory class containing a list of InventoryItem that each Node could have.

We could have used polymorphism to create many item classes like so Tool : InventoryItem, Pickaxe : Tool, FastPickaxe : Pickaxe, ... but that means we had to describe every single combination of item and item effect. And we don't even know yet what kind of effect we'll want to have on items in a few weeks.

That's where the decorator pattern came to our rescue. Wikipedia summarizes it like this:

The decorator pattern allows behavior to be added to an individual object without affecting the behavior of other objects from the same class.
– Wikipedia

This means that we can describe a very basic item with minimalist properties, such as a name and a description, and extend it's functionality by "decorating" it with new behaviors. Also it means we could have "decorator" that increases the movement speed and put it on any item without changing the item's class.

It sounds exactly like what we need.

Architecture and implementation

The BaseInventoryItem class is the uppermost class. It has a Name, a Description and a GetLongDescription method that will display... a long description of the item.

public abstract class BaseInventoryItem
{
    public string itemName { get; set; }
    public string description { get; set; }

    public BaseInventoryItem(string itemName, string description)
    {
        this.itemName = itemName;
        this.description = description;
    }

    public virtual string GetLongDescription()
    {
        return description;
    }
}

The BaseInventoryItemDecorator inherits from this class and also has a reference to an instance of his parent. It implements a few new methods and also overrides GetLongDescription by returning it's own description AND calling the base item's method. That's where the decorator pattern makes sense : by always calling the base item's method, we recursively make use of all the decorators we chained.

public class BaseInventoryItemDecorator : BaseInventoryItem
{
    protected BaseInventoryItem baseItem;

    protected BaseInventoryItemDecorator(BaseInventoryItem item)
    {
        baseItem = item;
        itemName = item.itemName;
        description = "Empty decorator";
    }

    public virtual void OnEquip(Node owner) { baseItem.OnEquip(owner); }
    public virtual void OnUnEquip(Node owner) { baseItem.OnUnEquip(owner); }
    public override string GetLongDescription()
    {
        return string.Format("{0}, {1}", baseItem.GetLongDescription(), description);
    }
}

Lastly, we have item decorators that inherits from BaseInventoryItemDecorator. In this example a MiningDecorator will increase the mining speed and a MovespeedDecorator will allow it's owner to run faster.

public class MiningSpeedItemDecorator : BaseInventoryItemDecorator
{
    private Buff speedBuff;

    public MiningSpeedItemDecorator(BaseInventoryItem item, float increasedSpeed) : base(item)
    {
        description = "improved mining speed";
        speedBuff = new Buff(BUFF_TYPE.ADDITIVE, increasedSpeed);
    }

    public override void OnEquip(Node owner)
    {
        baseItem.OnEquip(owner);
        owner.miningSpeed.AddBuff(speedBuff);
    }
    public override void OnUnEquip(Node owner)
    {
        baseItem.OnUnEquip(owner);
        owner.miningSpeed.RemoveBuff(speedBuff);
    }
}

Now we can create some items!

InventoryItem pickaxe = new InventoryItem("Pickaxe", "a dull pickaxe");
MiningSpeedItemDecorator fastPickaxe = new MiningSpeedItemDecorator(pickaxe, +10f);
MovespeedItemDecorator shoePickaxe = new MovespeedItemDecorator(fastPickaxe, +10f);

In the few example I gave, the decorators were pretty simple but one can override the OnUse method to... teleport the owner next to his target, or increase multiple stats at once, or change his skin and add some particles.

What's really great about this system is that once we have a few decorators and a few items, we can procedurally create all sort of new items without touching the existing code. We can also decorate items on the fly, allowing the player to improve the items he already has, or lose a decorator because he broke it.