# 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?
– user35344
Mar 19, 2018 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.