I am creating an ability system in Unity and it is currently able to update base values of stats and keep track of persistent buffs/debuffs while taking operation order into account (Add, Increase, Multiply, etc). I can also use the values of these stats to perform operations on other stats.
I wanted to make these stats update dynamically. For example, my character's Health
stat is equal to 0.5 * the value of the character's Strength
stat plus base Health
. So when Strength
updates, Health
also updates, meaning that Health
is dependent on Strength
.
A problem that I have encountered is handling cyclic dependencies.
Let's say that I want to add an item that increases the character's Strength
based on their Health
. Strength
is now dependent on Health
and when it updates, these two stats will update and infinitely loop, since when one stat changes, all dependencies must update too. This problem is also present in longer dependency chains too, e.g. stat A -> stat B -> stat C -> stat A.....
A solution I came up with was use a hash set of stats to check if the current chain of updates contains the current stat, and if it does, stop updating.
Is this a good solution? Or is there some key detail that I am missing when it comes to designing stat system that could innately prevent this kind of behavior?
I would like to know how others have approached a dynamically updating stat system since I can't seem to find any resources on this kind of thing other than Unreal Engine's poorly documented Gameplay Ability System.
Now obviously I could simply avoid designing items/mechanics that cause this type of behavior, but I do still need to prevent stack overflows.
-
4\$\begingroup\$ I would let the stats affect themself in layers. For example one layer could be item (that could be divided in raw stats of item and effect). First add all effects into a temp variable, once all effects from a layer are resolved, add them to your base stats. You wont have circular references that way, can easily add a lot of effects, buffs/ envirement effects etc and just need to solve in which order the layers are getting applied \$\endgroup\$Zibelas– Zibelas2025年05月13日 13:19:14 +00:00Commented May 13 at 13:19
-
1\$\begingroup\$ You may be interested in some past Q&A about dealing with cycles in stat effects triggering other effects (in this example, damage events). \$\endgroup\$DMGregory– DMGregory ♦2025年05月13日 21:19:52 +00:00Commented May 13 at 21:19
-
\$\begingroup\$ If you really want to, you can solve simultaneous equations. Could be a good synergy. Imagine equipping yourself with enough items to give strength += 0.9*health and health += 0.9*strength; You'd end up with 10 times your original strength and health, but no equipment slots left. \$\endgroup\$Stack Exchange Broke The Law– Stack Exchange Broke The Law2025年05月15日 09:53:57 +00:00Commented May 15 at 9:53
3 Answers 3
One solution can be to differ between the immutable base value of a stat and the modifiable modified value of a stat. That way you can have cyclic dependencies when those dependencies are on the base stat but not the modified stat. That would mean that Health
is defined as BaseHealth + BaseStrength * 0.5
and Strength
as BaseStrength + BaseHealth
. If you introduce the design constraint that stat modifiers may depend on base values but not on modified values, then there won't be any recursion loops.
Another option is to not calculate each stat separately but have one procedure to calculate all the stats in order. So you have a well-defined order in which the evaluation happens. Something like this pseudocode:
strength = 10
health = strength * 0.5
for each item in equippedItems
strength += item.strength
health += item.health
strength += health
If you want a more dynamic solution instead of hardcoding the whole stat derivation algorithm, then you could create an abstract base-class StatModifier
with a method modify
that takes a reference to the whole stat block. You would then call all these modifiers one after another, so they receive a modified stat block from those that came before and passes the further modified stat block to those that come afterwards. That way you won't have any cyclic dependencies, because each one only gets called once. But then it does of course matter in which order those modifiers get processed. You should probably formulate a rule for modifier priority in your game design document and make sure it's communicated to the player.
-
\$\begingroup\$ Thank you, I knew I was missing something and your first solution has pointed me in the right direction. My current Idea is to add another layer to this and have 3 types of values for each stat. They are Base Value, Modified Value, And Final Value. When the Base value updates, any derived stats of this base value will only update the Modified Value. Same thing with Modified Value changes updating Final Values of derived stats. This means That when I update a stats value, none of the derived stats will update the same value type meaning that there cant be any infinite loops. \$\endgroup\$Psycho ZXC– Psycho ZXC2025年05月14日 12:03:24 +00:00Commented May 14 at 12:03
-
\$\begingroup\$ @PsychoZXC As a variation, Dungeons & Dragons (at least, the editions thereof that I’m familiar with) has bonuses to its equivalent of "base stats," but those bonuses are always* fixed numbers (a headband of intellect might give +2 to Intelligence, and your spell attack might gain a bonus that scales with Intelligence, but nothing ever* gives a scaling bonus to Intelligence—so you’d certainly never be adding your spell attack bonus to your Intelligence). (* I do know one exception, but it was a bad idea.) \$\endgroup\$KRyan– KRyan2025年05月16日 03:37:10 +00:00Commented May 16 at 3:37
This isn't just a problem for video games; this can happen in board games too! So forget implementation details like hash sets for now. Your first problem is determining what the result you want will be.
Magic: The Gathering's "game engine" (running on the imagination of the judges, but a game engine nonetheless) tracks all the characteristic-changing effects that a creature is under and what order they must be applied in. Some take effect in the chronological order they were applied in, some take effect based on which characteristic they're changing, some take effect based on the source of the change... It's a maze of special cases which still changes occasionally when they add new game mechanics. Perhaps that's inevitable for a game of this complexity.
For example, effects that set power and toughness to a specific value are applied before effects that modify power and toughness based on its current value, which are applied before effects that switch power and toughness. So a 6/5 Bogstomper creature under the simultaneous effect of Reduce In Stature (which sets its stats to 0/2) and Giant Growth (which gives its current stats +3/+3) and About Face (which switches its stats) will be a 5/3. If the Reduce In Stature is disenchanted, the Bogstomper reverts to its base stats of 6/5 but the Giant Growth and About Face are still in effect, so its current stats are 8/9. If the player then triggers the effect of Mr Orfeo The Boulder which doubles the Bogstomper's power, this applies after Giant Growth and before About Face, so its stats are now 8/18.
Clear as mud?
There's no limit to how complicated this sort of system can be. It will help you a lot to decide exactly which effects you want to exist or not exist before you start building the system. (Pokémon's mechanics for in-combat stat changes are much simpler.)
Sometimes players will expect to be able to remove any given effect some time after it's applied, and for the remaining effects to be recalculated accordingly -- if you get a stat boost from a necklace, you should be able to simply take that necklace off and lose its boost. Other times the source of a stat change should be discarded and all such boosts treated as interchangeable -- e.g. if you earned 100 credits from selling Martian plants and 500 credits from bounty hunting, then you simply have 600 credits and the game doesn't track which is which. (Is "current wealth" a stat? Good question!)
Trust that you can make this as simple or as complicated as you want. Think of what items/mechanics you want your game to have, and program the system accordingly.
-
1\$\begingroup\$ +1 especially for the first paragraph. I think noticing and learning to mentally decouple mechanics design from implementation in code is a level-up moment for aspiring game developers. OP thinks they have a coding issue, but they have an unanswered mechanics design question. \$\endgroup\$xLeitix– xLeitix2025年05月16日 06:44:16 +00:00Commented May 16 at 6:44
Another idea which evaluates phillips answer even further: Add a second layer of stats. Stats of the second layer are determined by the stats of the first layer. One stat of the first layer may affect multiple stats of the second. But all stats in the first layer are independent. That way you don't have players wondering "Why doesn't item Strength increase my Health?" (see phillips example)
Example:
3 main stats: Constitution, Dexterity and Strength
5 secondary stats: Health, Walking Speed Attack Speed, Carrying Capacity and Armor
Health = 5x Constitution
Walking Speed = 5 + 0,2x Constitution + 0,1x Dexterity
Attack Speed = 2x Dexterity
Carrying Capacity = 2x Constitution + 2x Strength
Armor = X(Only obtainable from items) * 0,2 * Strength
That way you can also improve the secondary stats for a minor boost or the primary stats for a major boost. And you have 0 loops. You may even add some special effects like Evasion of Chriz, the legendary rogue: Dexterity now additionally affects Health with a factor of 2
You must log in to answer this question.
Explore related questions
See similar questions with these tags.