0
\$\begingroup\$

I want to create a few values which will be dependent on each other. Specifically, I want total sum of few values always be 100 In unity inspector no matter how I tweak them.

E.G.:

int total = 100;

int one = 20;
int two = 30;
int three = 45;
int four = 5;

How can I make other values adjust automatically if I tweak one of them in Unity Inspector?

\$\endgroup\$
  • \$\begingroup\$ If you'd change the value of one item to 0, how would the system know which other value to alter? Would you want it to be random? Distributed to all others? \$\endgroup\$ – Tyyppi_77 Mar 19 '18 at 12:34
1
\$\begingroup\$

You can provide an OnValidate() method, which gets called in the Editor when the user changes an Inspector value.

const int TOTAL = 100;
public int one, two, three, four;

#if UNITY_EDITOR
// Save their previous values so we can identify which one changed.
int _oneCache, _twoCache, _threeCache, _fourCache;
_oneCache = -1;

void OnValidate() {
    // Skip this if we haven't cached the values yet.
    if(_oneCache >= 0) {

        // Find which value the user changed, and update the rest from it.
        if(_oneCache != one) {
            DistributeProportionately(ref one, ref two, ref three, ref four);
        } else if (_twoCache != two) {
            DistributeProportionately(ref two, ref one, ref three, ref four);
        } else if()
            // ... repeat for three, four...
        }
    }

    // Cache the old values for the next edit.
    _oneCache = one;
    _twoCache = two;
    _threeCache = three;
    _fourCache = four;   
}

void DistributeProportionately(ref changed, ref int a, ref int b, ref int c) {
    changed = Mathf.Clamp(changed, 0, TOTAL);
    int total = TOTAL - changed;

    int oldTotal = a + b + c;
    if(oldTotal > 0) {
        float fraction = 1f/oldTotal;
        a = Mathf.RoundToInt(total * a * fraction);
        b = Mathf.RoundToInt(total * b * fraction);
        c = Mathf.RoundToInt(total * c * fraction);
    } else {
        a = b = c = total / 3;
    }

    // Assign any rounding error to the last one, arbitrarily.
    // (Better rounding rules exist, so take this as an example only)
    c += total - a - b - c;
}

#endif

This can be done with a custom editor too without explicitly caching each value (instead you'd use an EditorGUI.BeginChangeCheck to identify the changed field), but for quick data validation I like to keep it in one file.

If you're using these to ensure a set of probabilities add up to 100%, then you might want to consider treating the user-facing values as relative weights instead, and keep the normalized values internal:

[SerializeField]
[HideInInspector]
float[] _normalizedProbabilities;

#if UNITY_EDITOR
[SerializeField]
float[] _probabilityWeights;

void OnValidate() {
    float totalWeight = 0f;
    foreach(var weight in _probabilityWeights)
        totalWeight += weight;

    if(totalWeight == 0f)
        return;

    _normalizedProbabilities = new float[_probabilityWeights.Length];
    for(int i = 0; i < _probabilityWeights.Length; i++)
        _normalizedProbabilities[i] = _probabilityWeights[i]/totalWeight;
}
#endif

This tends to be a lot simpler to manage.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.