FAQ

Scriptable Variable

It seems like you might need to have every individual variable be its own scriptable object, so for 50 enemies, each enemy’s HP would be 50 different scriptable variables?

Yes. If you don’t want to create the variables beforehand, you can instantiate Scriptable variables at runtime containing the HP and pass it as a reference to your different systems (see Runtime Variables and Runtime Injectors).

However, maybe that is not the best approach. Scriptable variables are best for things that have dependencies with other systems but are singular. It suits more for things like Player HP or a Boss HP as well. For many enemies maybe it's overkill.

Most of the time, if there are a lot of enemies, you don’t really want or need to solve dependency with their individual stats. Instead, the most common use case is to create a Scriptable List of enemies that you can reference anywhere (solving most of your dependencies) and access their stats there.

Why OnValueChanged doesn't trigger when you modify a field in a custom C# class ? (And how to fix it)

If you define a class like this:

[System.Serializable]
public class Stats
{
    public float Speed;
    public int Damage;
}

And you create a Scriptable Variable of that class:

[CreateAssetMenu(menuName = "Examples/StatsVariable")]
public class StatsVariable : ScriptableVariable<Stats>
{
    
}

You might expect that modifying any of the fields (e.g., Speed or Damage) would trigger the OnValueChanged event. However, this doesn't happen by default due to two important reasons:

  1. ScriptableVariable only triggers the event when the reference changes

    The default implementation uses references to compare the current and new values. If the reference doesn’t change, the comparison considers it “equal” — even if internal fields were modified.

  2. OnValueChanged is only fired by the .Value setter

    When you mutate a field directly, you're not reassigning .Value, so the setter isn't called and no event is fired.

For example:

var old = _stats.Value;
old.Speed = 10;    // ❌ No setter call → no event
_stats.Value = old; // ✅ Setter call → event fires  

âś… How to Trigger the Event on Field Mutation

If you're modifying a field like this:

_stats.Value.Speed += 1;

…the event won’t fire unless you explicitly notify the variable. There are two ways to fix this:

1. Reassign a New Object to .Value

You can manually assign a new instance of the class to .Value with the updated data:

var copy = _stats.Value;
_stats.Value = new Stats { Speed = 10, Damage = copy.Damage }; // âś… Fires event

This works because you're assigning a new reference, and the setter compares the new value to the old one.

âś… 2. Call NotifyChanged() After Mutation

Manually tell the Scriptable Variable that its value has changed:

_stats.Value.Speed = 10;
_stats.NotifyChanged(); // âś… Manually fires the event

Full Example:


[SerializeField] private StatsVariable _stats;
private void Start()
{
    _stats.OnValueChanged += OnStatsChanged; 
    
    // ✅ Assignment via setter – event fires:
    var copy = _stats.Value;
    _stats.Value = new Stats { Speed = 10, Damage = copy.Damage };
    
    // âś… or manually notifying ValueChanged after setting a field
    _stats.Value.Speed = 10;
    _stats.ValueChanged();
    
}

private void OnStatsChanged(Stats value)
{
     Debug.Log($"Stats changed: Speed = {value.Speed}, Damage = {value.Damage}");
}

private void OnDestroy()
{
    _stats.OnValueChanged -= OnStatsChanged; 
}

đź’ˇ Optional: Use struct Instead of class

If you define your custom class can be defined as a struct instead, every field mutation will require reassignment — which naturally triggers the setter. This avoids manual NotifyChanged() calls and helps reduce heap allocations.

Last updated