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.