Character Stats/Attributes in Unity (pt. 2) — Additive Modifiers & Optimizations

Kryzarel
4 min readDec 4, 2017

--

This is part 2 of a tutorial on character stats. Check out part 1 here:

There’s also a video version of this tutorial if you prefer that:

Video version of Character Stats pt.2

Right now our percentage modifiers stack multiplicatively with each other, i.e., if we add two 100% modifiers to a stat, we won’t get 200%, we’ll get 400%. Because the first will double our original value (going from 100% to 200%), and the second will double it again (going from 200% to 400%).

But what if we would like to have certain modifiers stack additively? Meaning that the previous example would result in a 200% bonus instead of 400%.

Let’s add a third type of modifier by changing our StatModType enum to this:

Don’t forget to change Percent to PercentMult in the CalculateFinalValue() method (inside the CharacterStat class). Or just use Visual Studio’s renaming features to do it for you ^^.

Inside the CalculateFinalValue() method, we need to add a couple of things to deal with the new type of modifier. It should now look like this:

This time the calculation gets pretty weird. Basically, every time we encounter a PercentAdd modifier, we start adding it together with all modifiers of the same type, until we encounter a modifier of a different type or we reach the end of the list. At that point, we grab the sum of all the PercentAdd modifiers and multiply it with finalValue, just like we do with PercentMult modifiers.

For the next bit, let’s add a Source variable to our StatModifier class. This way, later on when we actually have stuff in our game that adds modifiers (like items and spells), we’ll be able to tell where each modifiers came from.

This could be useful both for debugging and also to provide more information to the players, allowing them to see exactly what is providing each modifier.

The StatModifier class should look like this:

Let’s say we equipped an item that grants both a flat +10 and also a +10% bonus to Strength. The way the system works currently, we have to do something like this:

But now that our modifiers have a Source, we can do something useful in CharacterStat — we can remove all modifiers that have been applied by a certain Source at once. Let’s add a method for that:

Why is the for loop in reverse?
To explain this let’s look at what happens when we remove the first object from a list vs when we remove the last object:

Let’s imagine we have a list with 10 objects, when we remove the first one, the remaining 9 objects will be shifted up. That happens because index 0 is now empty, so the object at index 1 will move to index 0, the object at index 2 will move to index 1, and so on. As you can imagine, this is quite inefficient.

However, if we remove the last object, then nothing has to be shifted. We just remove the object at index 9 and everything else stays the same.

This is why we do the removal in reverse. Even if the objects that we need to remove are in the middle of the list (where shifts are inevitable), it’s still a good idea to traverse the list in reverse (unless your specific use case demands otherwise). Any time more than one object is removed, doing it from last to first always results in less shifts.

And now we can add and remove our (hypothetical) item’s modifiers like so:

Since we’re talking about removing modifiers, let’s also “fix” our original RemoveModifier() method. We really don’t need to set isDirty every single time, just when something is actually removed.

We also talked about letting players see the modifiers, but the statModifiers list is private. We don’t want to change that because the only way to safely modify it is definitely through the CharacterStat class. Fortunately, C# has a very useful data type for these situations: ReadOnlyCollection.

Make the following changes to CharacterStat:

The ReadOnlyCollection stores a reference to the original List and prohibits changing it. However, if you modify the original statModifers (lowercase “s”), then the StatModifiers (uppercase “S”) will also change.

To finish up part 2, I’d like to add just two more things. We left the BaseValue as public, but if we change it, it won’t cause the Value property to recalculate. Let’s fix that.

The other thing is in the StatModType enum. Let’s override the default “indexes” of the enum values, like so:

The reason for doing this is simple — if someone wants to add a custom Order value to make some modifiers sit in the middle of the default ones, this allows a lot more flexibility.

If we want to add a Flat modifier that applies between PercentAdd and PercentMult, we can just assign an Order anywhere between 201 and 299.
Before we made this change, we’d have to assign custom Order values to all PercentAdd and PercentMult modifiers too.

And that’s it for part 2. Don’t hesitate to drop a comment if you have any questions, suggestions or feedback.

I hope this has been helpful, and goodbye for now!

--

--