1
\$\begingroup\$

I have four sources of a character's stats changing (in order of calculation):

  1. Due to leveling up (base stats)
  2. Equipment changing (calculated after base stats)
  3. A passive skill being toggled on or off (calculated after equipment stats)
  4. Being afflicted by a buff/debuff (calculated after passive skill stats)

My question is, if any of these sources causes a change, should I recalculate everything from base stats, or store "sets" of the stats after each change (i.e. save a copy of the stats after passive skill changes, so if the player is hit by a "defense down" status effect, I only have to recalculate from skill stats). Unless there's a better way to deal with this problem, I don't know which of these two options is better.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Recalculating all your stats isn't a major CPU hog, I suspect that unless you have hundreds of thousands of characters receiving buffs at once or whatever, it's not going to be a big deal to recalculate everything from scratch every time. \$\endgroup\$ – Foxwarrior Mar 15 '20 at 0:33
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.