Looking for a C# pattern to automatically perform checkes and updates on different stats

Hi all,

I have a global stats singleton that stores game data and performs some checks and update events if they get changed. Currently, the access code looks like this:

public event Action AttackSpeedChanged;
public float minAttackSpeed = 0.1f;
public float maxAttackSpeed = 2.0f;
private float _oldAttackSpeed = 0.2f;
private float _attackSpeed = 0.2f;
public float attackSpeed
{
	get { return _attackSpeed; }
	set
	{
		float clampedValue = Mathf.Clamp(value, minAttackSpeed, maxAttackSpeed);
		if (clampedValue != _oldAttackSpeed)
		{
			_attackSpeed = clampedValue;
			_oldAttackSpeed = _attackSpeed;
			AttackSpeedChanged?.Invoke();
		}
	}
}

I need to have like 20 of these in different data types (mostly int ant float) and I am sure there’s a pattern to create some template function that only needs some instancing, so instead of having all these duplicates I can boil it down to fewer calls and most importantly don’t need to change 20 functions if I change my architecture. What can I try?

What you can do is create a wrapper class for your stats, which has the checks and updates built-in. Then in your classes you’d use these wrappers instead of direct types. Something like this:

[Serializable]
public class StatFloat
{
	public event Action OnValueChanged;
	public float MaxValue;
	public float MinValue;
	
	[SerializeField] private float _value; // it's still private, but this attribute makes it editable in the Inspector
	private float _oldValue;

	public float Value
	{
		get
		{
			return _value;
		}
		set
		{
			_oldValue = _value;
			_value = Mathf.Clamp(value, MinValue, MaxValue);

			// floating point comparison fix - read more here: https://www.jetbrains.com/help/resharper/CompareOfFloatsByEqualityOperator.html
			if (Math.Abs(_oldValue - _value) > Mathf.Epsilon)
			{
				OnValueChanged?.Invoke();
			}
		}
	}
}

You can then declare in your other classes things like public StatFloat AttackSpeed; and subscribe to its OnValueChanged event. Remember that you’ll access the actual value with AttackSpeed.Value in this case.