Skip to main content
deleted 3 characters in body
Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401
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/(a + b + c);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
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/(a + b + c);
        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
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
Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401

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/(a + b + c);
        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.