Stats 03: Modifiers

In this section we’ll be creating stat modifiers. These modifiers will be applied to a stat by an item, buff or spell and will change the StatValue property of the stat to reflect the value of the modifier. In this initial version of the modifier class we’ll use a enum to determine how the modifier is applied to the stat (In future section we’ll update the modifer design to allow for easier addition of future modifier types).

This post is an old implementation and should be only used as a reference. This post maybe updated or replaced in the future.

The Stat Modifier

The stat modifier will consist of three main variables with their corresponding properties: the type of modifier, the value of the modifier, and the stat the modifier will effect.

public class RPGStatModifier {
    public enum Types {
        None,
        BaseValuePercent,
        BaseValueAdd,
        TotalValuePercent,
        TotalValueAdd,
    }

    private Types _type;
    private float _value;
    private RPGStatType _statType;

    public Types Type {
        get { return _type; }
        set { _type = value; }
    }

    public float Value {
        get { return _value; }
        set { _value = value; }
    }

    public RPGStatType StatType {
        get { return _statType; }
        set { _statType = value; }
    }

    public RPGStatModifier() {
        _type = Types.None;
        _value = 0;
        _statType = RPGStatType.None;
    }

    public RPGStatModifier(RPGStatType targetStat, Types modType, float value) {
        _type = modType;
        _value = value;
        _statType = targetStat;
    }    
}

The Type property will be used inorder to determine how the Value property will be applied to the stat’s base value. The RPGStatType will mostly be used in later sections when we start to apply modifiers from external scripts.

Modifiable Interface

Next we will be creating an interface that will contain all the features any stat that will be affected by stat modifiers will need to implement in order to work. Also we will use interfaces throughout the series in order to tell which stats implement which features so we don’t need to know the type of the script (since each additional script could potentially have to be checked), but simply if the stat implements the interface.

public interface IStatModifiable {
    int StatModifierValue { get; }

    void AddModifier(RPGStatModifier mod);
    void ClearModifiers();
    void UpdateModifiers();
}

Each stat that implements that is allowed to be modified by stat modifiers will have the StatModifierValue property which is the total value of all modifiers currently attached to the stat. The AddModifier and ClearModifiers methods that will allow us to add and remove modifiers. Finally the UpdateModifiers method which will update the StatModifierValue property with the current value of the modifiers attached to the stat.

Modifiable Stat

Next well implement the interface into a class that will inherit the RPGStat class. The new RPGStatModifiable class will implement the interface and will handle calculating the stat modifier’s values.

using System.Collections.Generic;
public class RPGStatModifiable : RPGStat, IStatModifiable{
    private List<RPGStatModifier> _statMods;
    private int _statModValue;

    public override int StatValue {
        get { return base.StatValue + StatModifierValue;}
    }

    public int StatModifierValue {
        get { return _statModValue; }
    }

    public RPGStatModifiable() {
        _statModValue = 0;
        _statMods = new List<RPGStatModifier>();
    }

    public void AddModifier(RPGStatModifier mod) {
        _statMods.Add(mod);
    }

    public void ClearModifiers() {
        _statMods.Clear();
    }

    public void UpdateModifiers() {
        _statModValue = 0;
        float statModBaseValueAdd = 0;
        float statModBaseValuePercent = 0;
        float statModTotalValueAdd = 0;
        float statModTotalValuePercent = 0;

        foreach (RPGStatModifier mod in _statMods) {
            switch (mod.Type) {
                case RPGStatModifier.Types.BaseValueAdd:
                    statModBaseValueAdd += mod.Value;
                break;
                case RPGStatModifier.Types.BaseValuePercent:
                    statModBaseValuePercent += mod.Value;
                break;
                case RPGStatModifier.Types.TotalValueAdd:
                    statModTotalValueAdd += mod.Value;
                break;
                case RPGStatModifier.Types.TotalValuePercent:
                    statModTotalValuePercent += mod.Value;
                break;
            }
        }

        _statModValue = (int)((StatBaseValue * statModBaseValuePercent) + statModBaseValueAdd);
        _statModValue += (int)((StatValue * statModTotalValuePercent) + statModTotalValueAdd);
    }
}

The most important change addition to the RPGStatModifiable class is the overrided property StatValue. This override takes the base class’s version of the property then add the StatModifierValue property to that. This change makes the StatValue property’s value contain the modifiers value added to the stat’s base value.

Testing Modifiable Stats

In the testing code below all we do is display all the stat’s values, then add modifiers to a given stat. After the modifiers are added we update the modifer’s value and finally display all the stat’s values again.

public class RPGStatTest : MonoBehaviour {
    private RPGStatCollection stats;

    void Start () {
        stats = new RPGDefaultStats();

        var statTypes = Enum.GetValues(typeof(RPGStatType));
        foreach (var statType in statTypes) {
            RPGStat stat = stats.GetStat((RPGStatType)statType);
            if (stat != null) {
                Debug.Log(string.Format("Stat {0}’s value is {1}",
                stat.StatName, stat.StatValue));
            }
        }

        var health = stats.GetStat<RPGStatModifiable>(RPGStatType.Health);
        health.AddModifier(new RPGStatModifier(RPGStatType.Health, RPGStatModifier.Types.BaseValuePercent, 1.0f)); // 200
        health.AddModifier(new RPGStatModifier(RPGStatType.Health, RPGStatModifier.Types.BaseValueAdd, 50f)); // 250
        health.AddModifier(new RPGStatModifier(RPGStatType.Health, RPGStatModifier.Types.TotalValuePercent, 1.0f)); // 500
        health.UpdateModifiers();

        foreach (var statType in statTypes) {
            RPGStat stat = stats.GetStat((RPGStatType)statType);
            if (stat != null) {
                Debug.Log(string.Format("Stat {0}’s value is {1}",
                stat.StatName, stat.StatValue));
            }
        }
    }
}

Updating the Testing class

Updating our testing class with Generics and Lambda Expressions to allow use to easily loop through all our stat types.

public class RPGStatTest : MonoBehaviour {
    private RPGStatCollection stats;

    void Start () {
        stats = new RPGDefaultStats();

        DisplayStatValues();

        var health = stats.GetStat<RPGStatModifiable>(RPGStatType.Health);
        health.AddModifier(new RPGStatModifier(RPGStatType.Health, RPGStatModifier.Types.BaseValuePercent, 1.0f)); // 200
        health.AddModifier(new RPGStatModifier(RPGStatType.Health, RPGStatModifier.Types.BaseValueAdd, 50f)); // 250
        health.AddModifier(new RPGStatModifier(RPGStatType.Health, RPGStatModifier.Types.TotalValuePercent, 1.0f)); // 500
        health.UpdateModifiers();

        DisplayStatValues();
    }

    void ForEachEnum<T>(Action<T> action) {
        if (action != null) {
            var statTypes = Enum.GetValues(typeof(T));
            foreach (var statType in statTypes) {
                action((T)statType);
            }
        }
    }

    void DisplayStatValues() {
        ForEachEnum<RPGStatType>((statType) => {
            RPGStat stat = stats.GetStat((RPGStatType)statType);
            if (stat != null) {
                Debug.Log(string.Format("Stat {0}’s value is {1}",
                stat.StatName, stat.StatValue));
            }
        });
    }
}

2 thoughts on “Stats 03: Modifiers”

  1. The percent behaviour it’s kinda strange , you’re setting it to 1 which should mean an increase of 1% over the stat value , in the case of 100 should be to 101 , but it’s behaving as a multiplier of the base stat.

    I think is more common to have a real percent of the item’s rather than what’s here implemented.

    Btw the code in the blog doesn’t match the one in the video , it has some compile errors.

  2. /// <summary>
    /// Removes modifier from stat and stops listening to value change event
    /// </summary>
    public void RemoveModifier(RPGStatModifier mod) {
    _statMods.Add(mod); <<< —————- Add?????
    mod.OnValueChange -= OnModValueChange;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *