What happens more often:
- Changing something which causes a derived stat to require recalculation
- Reading the value of a derived stat
We don't know anything about the architecture of your game, so we can only guess.
If changes are more frequent than reads, then it can make sense to make each derived stat a property which get recalculated on the fly. (A property looks like a variable from the outside, but internally includes logic when you set and/or get its value).
Something like this:
public float Attack {
get => ( strength * level ) + weapon.damage;
}
From a code readability and maintainability standpoint I find this a quite clean solution. You can immediately see that Attack is a derived attribute and how it's calculated. If performance is not a concern, I would recommend you to keep doing it that way.
However, in most games, character attributes are read far more frequently than they are changed. That means the attack value will get recalculated again and again even though the variables it is based on didn't change. Often this won't be a concern. As long as you only have a small number of objects fighting in the scene, you won't even notice the performance overhead. But if you want to create huge battle simulations with thousands of objects, then it could become noticeable.
In that case it might make more sense to turn the base attributes into properties instead and trigger the recalculation in their setters. The frequently called derived attributes become read-only properties which return the cached values:
private float _attack;
public float Attack {
get => _attack
}
private float _strength;
public float Strength {
get => _strength;
set {
_strength = value;
recalculateAttack();
}
}
private void recalculateAttack() {
_attack = ( _strength * level ) + weapon.damage;
}
But while this code is more optimized for frequent reads, it also has a lot more boilerplate. There is also some room for bugs. It's not unlikely that some developer will at some point during development add some obscure way to change a variable which contributes to _attack
without triggering the recalculateAttack()
method. You might not even notice when you test it, because in your test setup there is something else which happens immediately afterwards which triggers a recalculation. But that might not be guaranteed to happen in every situation.
Do you want to get exploits in your game? Because that's how you get exploits.