# Creating dependent values in the Unity inspector

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?

• 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? Mar 19 '18 at 12:34

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.

Here is my solution for it, which also supports locking elements so that changing one, does not affect every other value.

private bool[] _lockStates;
public override void OnInspectorGUI()
{
var data = (TrickChargeButtonsController) target;
base.OnInspectorGUI();

if (_lockStates == null || _lockStates.Length != data.normalizedValues.Length)
{
_lockStates = Enumerable.Range(0,data.normalizedValues.Length).Select( n=>false).ToArray();
}

if (GUILayout.Button("Normalize"))
{
for (int i = 0; i < data.normalizedValues.Length; i++)
{
data.normalizedValues[i] = (float)1.0f / (float)data.normalizedValues.Length;
}
}

float totalLength = 1.0f;
for (int i = 0; i < data.normalizedValues.Length;i++)
{
var lockedCount = _lockStates.Sum(isLocked => isLocked ? 1 : 0);
var prior = data.normalizedValues[i];
GUILayout.BeginHorizontal();
var oldValue = data.normalizedValues[i];
data.normalizedValues[i] = GUILayout.HorizontalSlider(data.normalizedValues[i],
0f,
totalLength);
if (_lockStates[i] || lockedCount >= data.normalizedValues.Length-1)
{
data.normalizedValues[i] = oldValue;
}
if (GUILayout.Button(_lockStates[i] ? "unlock" : "lock"))
{
_lockStates[i] = !_lockStates[i];
}
GUILayout.EndHorizontal();
var after  = data.normalizedValues[i];
if (lockedCount < data.normalizedValues.Length-1)
{
var delta = (after - prior)/(data.normalizedValues.Length-lockedCount-1);
for (int j = 0; j < data.normalizedValues.Length; j++)
{
if (i != j && !_lockStates[j])
{
var test = data.normalizedValues[j] - delta;
if (test < 0f)
{ // we cant allow this to happen , backtrack previous value.