SkillAgentSearch skills...

ModifiedValues

A powerful C# library for Unity that enables modifying (numeric and other) values while keeping track and managing the modifiers affecting them. Great for games with buffs/debuffs, stats, statuses, perks, equipment etc.

Install / Use

/learn @Improx/ModifiedValues
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ModifiedValues

Unity 2021.2+ License: MIT

ModifiedValues is a powerful C# library for Unity that enables modifying (numeric and other) values while keeping track and managing the modifiers affecting them. Great for games with buffs/debuffs, stats, statuses, perks, equipment etc.

This can be especially useful in a buff / stat effects system, where different modifiers affect the value of different numbers, there can be many different modifiers per number, and it doesn't matter in which temporal order the modifiers were added.

This system is designed to be as generic and extendable as possible, enabling a wide variety of use cases. Many out-of-the-box classes and function implementations are provided, to enable the most common use cases quickly.

Minimum requirement is <strong>Unity 2021.2</strong> (for C# 9 and netstandard2.1).

This can also be quite easily used as a non-Unity C# library by removing only a few things. See the last section for more info on this.

Installation

You can install this as a Unity Package by going to Window -> Package Manager, clicking the plus sign, "Add package from git URL" and pasting https://github.com/Improx/ModifiedValues.git?path=/Assets/ModifiedValues.

Quickstart Example

Make sure to use the ModifiedValues namespace.

You're making a buff system for your game. Instead of having a classic float variable on your character

public float Speed = 10;

You can create a Modified version of it:

public ModifiedFloat Speed = 10;

If the field is public or private serialized, it will also appear in the inspector:

alt text

For convenience, this Speed object can be implicitly cast back into a float. Most of your code can treat it as just a regular float value:

transform.position += Speed * Time.deltaTime;

In rare cases where that is not possible, you can get the float value by Speed.Value.

Let's say your character gets an Energized buff that multiplies base speed by 120%. Your character also equips rollerscates, increasing speed by 5. You apply these multiplicative and additive modifiers like this:

Speed.Add(5);
Speed.Mul(1.2f);

By default, in this library the multiplicative modifier is applied before the additive one (although you can easily change this based on your needs, read below about Layer, Priority and Order). The above code results in a final speed value of 17, regardless of the order those two lines of code are written.

Your code that uses Speed will automatically pick up the updated value. The current value is also visible in the inspector:

alt text

If you want to be able to remove these buffs later, you need to save the modifier objects:

Modifier energizedBuff = Speed.Mul(1.2f);
Modifier rollerSkatesBuff = Speed.Add(5);

//After some time passes, you want to remove the Energized buff.
energizedBuff.DetachFromAll();

Debug.Log(Speed); //Will print 15

Without this library, where Speed is just a normal float, you would have needed to do something like this:

Speed *= 1.2f;
Speed += 5;

//After some time passes, you want to remove the Energized buff.
//However, we can't just simply divide by 1.2f to get the correct result, because
//the rollerskates buff is still active
//We need to keep the additive rollerskates effect like this:

Speed -= 5;
Speed /= 1.2f;
Speed += 5;

Debug.Log(Speed); //Will print 15

With many different kinds of buffs, doing this manually could get extremely convoluted. One of the main conveniences of this library is that the buffs don't have to know about each other. For each modifier, you just define how it modifies the value, and then you can attach and detach these modifiers independently, while keeping the final value always correct. You also don't need to worry about the temporal order in which you apply modifiers. The ordering, layers and priorities of modifiers are defined in optional parameters (explained further down).

This library provides the following wrapper types with lots of ready functionality, and you can easily create more:

  • ModifiedFloat
  • ModifiedDouble
  • ModifiedDecimal
  • ModifiedInt
  • ModifiedUint
  • ModifiedLong
  • ModifiedUlong
  • ModifiedBool

You can wrap other types without needing to create new classes simply by using ModifiedValue<MyType>, it just won't have as much functionality by default. For example ModifiedFloat just inherits from ModifiedValue<float> and adds a bunch of methods on top, such as Add and Mul. You are of course free to use ModifiedValue<float>, but ModifiedFloat just has so many ready helper methods. See section "Out-of-the-box Modifiers" below for a full list of helper methods to cover the most common use cases.

With a generic wrapper for any type, you can apply modifiers with any custom operations. The ModifiedValues system is not limited to cover only the above-mentioned types, but you can create ModifiedValues and Modifiers wrapping any type you want (that don't even need to be numeric):

ModifiedValue<MyType> myValue = new MyType();
Modifier mod = myValue.ModifyLatest((latestValue) => latestValue * 1.2f + 5);

Initialization

You can create a new ModifiedValue object in many ways. You can do it with a contructor, where you pass the base value as a parameter. Implicitly setting a ModifiedValue object to a base value does the same thing. You can also call the constructor with a base value getter function parameter, in which case the base value can have external dependencies (for example, the base value can depend on the value of another ModifiedValue).

ModifiedFloat Speed1 = 5;
//Has the same base value as:
ModifiedFloat Speed2 = new ModifiedFloat(5);
//Has the same base value as:
ModifiedFloat Speed3 = new ModifiedFloat(() => 5);
//Has the same base value as:
ModifiedFloat Speed4 = new ModifiedFloat(ReturnFive);

private float ReturnFive()
{
  return 5;
}

A use case for a base value getter function instead of a hard value is when you want the base value to have an external dependency that can change over time.

Note that if at a later stage, you set a ModifiedValue object to a new base value implicitly again, the reference will point to a completely new ModifiedValue object.

ModifiedFloat Speed = 5;
Speed.Add(1);
Speed = 3; //Speed is now a completely new object, with a base value of 3 and the previous Add modifier removed.

If you want to update a ModifiedValue's base value while keeping all modifiers, you can update its BaseValue or BaseValueGetter function directly.

:warning: Uninitialized ModifiedValue references = bad! :warning:

[Serializedfield] private ModifiedFloat Speed; //Set this reference to a new ModifiedFloat before using it!

Declaring a serialized ModifiedValue member variable and not assigning anything to it leads to Unity creating a default object out of it, instead of keeping the reference as null. In this Unity quirk, the constructor is bypassed and the ModifiedValue is not initialized correctly. Using such ModifiedValue objects will result in errors. Always set it to something when declaring it, or later. If needed, you can check whether a ModifiedValue object was created in this bad way (in that case ModifiedValue.Init equals false) and replace it with a new object. The inspector also alerts if a ModifiedValue is uninitialized:

alt text

Out-of-the-box Modifiers

The following modifying methods are readily available for ModifiedFloat, ModifiedDouble and ModifiedDecimal:

  • Set(): Forces to this value.
  • AddFraction(): Adds this fraction of the value (relative to what the value was at the beginning of the layer). Multiple modifiers of this kind stack additively.
  • Mul(): Multiplies the value by this amount. Multiple modifiers of this kind stack multiplicatively.
  • Add(): Adds this value. Can be negative.
  • MaxCap(): Limits value from above.
  • MaxCapFinal(): Limits value from above. Is applied with priority and layer equaling to int.MaxValue.
  • MinCap(): Limits value from below.
  • MinCapFinal(): Limits value from below. Is applied with priority and layer equaling to int.MaxValue.

Available for ModifiedInt. ModifiedUint, ModifiedLong and ModifiedUlong:

  • Set()
  • AddMultiple(): Adds this multiple of the value (relative to what the value was at the beginning of the layer). Multiple modifiers of this kind stack additively.
  • Mul()
  • Add()
  • MaxCap()
  • MaxCapFinal()
  • MinCap()
  • MinCapFinal()

Available for ModifiedBool:

  • Set()
  • Not(): Applies a Not logic gate.
  • And(): Applies a And logic gate.
  • Or(): Applies a Or logic gate.
  • Xor(): Applies a Xor logic gate.
  • Imply(): Applies a Imply logic gate.

Note: ALL ModifiedValue<T> types have the Set() Modifier readily available, not just in the abovementioned predefined helper classes.

If many different modifiers are applied that have the same Priorityand Layer, they will all have effect. They will be applied in the same order as they are presented in the lists above (from top to bottom). This ordering is also visible in the DefaultOrders.cs cl

Related Skills

View on GitHub
GitHub Stars8
CategoryDevelopment
Updated2mo ago
Forks2

Languages

C#

Security Score

85/100

Audited on Jan 15, 2026

No findings