0
\$\begingroup\$

I have several values which adjust them self In Unity Inspector when I tweak one of them. So total sum of these values stays constant. (which is 100 as 100%)

enter image description here

Sometimes it is hard to get specific values on more when one slider. How can I lock value so it can't be affected by adjusting other values?

private const float TOTAL_STARS_SHARE = 100.0f; //total share of all stars

// #0
[Tooltip("Share of Blue Star Hard prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfBlueStarHard;
private float shareOfBlueStarHardCACHE; //cache for value with same name

// #1
[Tooltip("Share of Blue Star Medium prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfBlueStarMedium;
private float shareOfBlueStarMediumCACHE; //cache for value with same name

// #2
[Tooltip("Share of Blue Star Soft prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfBlueStarSoft;
private float shareOfBlueStarSoftCACHE; //cache for value with same name

// #3
[Tooltip("Share of Red Star Soft prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfRedStarSoft;
private float shareOfRedStarSoftCACHE; //cache for value with same name

// #4
[Tooltip("Share of White Star Hard prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfWhiteStarHard;
private float shareOfWhiteStarHardCACHE; //cache for value with same name

// #5
[Tooltip("Share of White Star Medium prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfWhiteStarMedium;
private float shareOfWhiteStarMediumCACHE; //cache for value with same name

// #6
[Tooltip("Share of White Star Soft prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfWhiteStarSoft;
private float shareOfWhiteStarSoftCACHE; //cache for value with same name

// #7
[Tooltip("Share of Yellow Star Soft prefabs in percentages.")]
[Range(0f, 100f)]
[SerializeField]
private float shareOfYellowStarSoft;
private float shareOfYellowStarSoftCACHE; //cache for value with same name

/* Controls sliders for stars shares in Unity Inspector. Doesn't work in actual game.*/
#if UNITY_EDITOR
private void OnValidate()
{
    ValidateStarsShares();

}

private void ValidateStarsShares() // Find which value the user changed, and update the rest from it.
{
    // Skip this if we haven't cached the values yet.
    if (shareOfBlueStarHardCACHE >= 0)
    {

        // Find which value the user changed, and update the rest from it.
        if (shareOfBlueStarHardCACHE != shareOfBlueStarHard) // #0 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfBlueStarSoft, ref shareOfRedStarSoft,
               ref shareOfWhiteStarHard, ref shareOfWhiteStarMedium, ref shareOfWhiteStarSoft, ref shareOfYellowStarSoft);
        }
        else if (shareOfBlueStarMediumCACHE != shareOfBlueStarMedium) // #1 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfBlueStarMedium, ref shareOfBlueStarHard, ref shareOfBlueStarSoft, ref shareOfRedStarSoft,
               ref shareOfWhiteStarHard, ref shareOfWhiteStarMedium, ref shareOfWhiteStarSoft, ref shareOfYellowStarSoft);
        }
        else if (shareOfBlueStarSoftCACHE != shareOfBlueStarSoft) // #2 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfBlueStarSoft, ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfRedStarSoft,
               ref shareOfWhiteStarHard, ref shareOfWhiteStarMedium, ref shareOfWhiteStarSoft, ref shareOfYellowStarSoft);
        }
        else if (shareOfRedStarSoftCACHE != shareOfRedStarSoft) // #3 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfRedStarSoft, ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfBlueStarSoft,
               ref shareOfWhiteStarHard, ref shareOfWhiteStarMedium, ref shareOfWhiteStarSoft, ref shareOfYellowStarSoft);
        }
        else if (shareOfWhiteStarHardCACHE != shareOfWhiteStarHard) // #4 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfWhiteStarHard, ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfBlueStarSoft,
               ref shareOfRedStarSoft, ref shareOfWhiteStarMedium, ref shareOfWhiteStarSoft, ref shareOfYellowStarSoft);
        }
        else if (shareOfWhiteStarMediumCACHE != shareOfWhiteStarMedium) // #5 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfWhiteStarMedium, ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfBlueStarSoft,
               ref shareOfRedStarSoft, ref shareOfWhiteStarHard, ref shareOfWhiteStarSoft, ref shareOfYellowStarSoft);
        }
        else if (shareOfWhiteStarSoftCACHE != shareOfWhiteStarSoft) // #6 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfWhiteStarSoft, ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfBlueStarSoft,
               ref shareOfRedStarSoft, ref shareOfWhiteStarHard, ref shareOfWhiteStarMedium, ref shareOfYellowStarSoft);
        }
        else if (shareOfYellowStarSoftCACHE != shareOfYellowStarSoft) // #7 star share changed
        {
            DistributeProportionatelyForStars(ref shareOfYellowStarSoft, ref shareOfBlueStarHard, ref shareOfBlueStarMedium, ref shareOfBlueStarSoft,
               ref shareOfRedStarSoft, ref shareOfWhiteStarHard, ref shareOfWhiteStarMedium, ref shareOfWhiteStarSoft);
        }
    }

    // Cache the old values for the next edit.
    shareOfBlueStarHardCACHE = shareOfBlueStarHard;
    shareOfBlueStarMediumCACHE = shareOfBlueStarMedium;
    shareOfBlueStarSoftCACHE = shareOfBlueStarSoft;
    shareOfRedStarSoftCACHE = shareOfRedStarSoft;
    shareOfWhiteStarHardCACHE = shareOfWhiteStarHard;
    shareOfWhiteStarMediumCACHE = shareOfWhiteStarMedium;
    shareOfWhiteStarSoftCACHE = shareOfWhiteStarSoft;
    shareOfYellowStarSoftCACHE = shareOfYellowStarSoft;
}

void DistributeProportionatelyForStars(ref float changed, ref float one, ref float two, ref float three,
    ref float four, ref float five, ref float six, ref float seven)
{
    changed = Mathf.Clamp(changed, 0.0f, TOTAL_STARS_SHARE);
    float total = TOTAL_STARS_SHARE - changed;

    float oldTotal = one + two + three + four + five + six + seven;

    if (oldTotal > 0)
    {
        float fraction = 1f / oldTotal;
        one = Mathf.RoundToInt(total * one * fraction);
        two = Mathf.RoundToInt(total * two * fraction);
        three = Mathf.RoundToInt(total * three * fraction);
        four = Mathf.RoundToInt(total * four * fraction);
        five = Mathf.RoundToInt(total * five * fraction);
        six = Mathf.RoundToInt(total * six * fraction);
        seven = Mathf.RoundToInt(total * seven * fraction);
    }
    else
    {
        one = two = three = four = five = six = seven = total / 7;
    }

    // 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
\$\endgroup\$
2
  • 1
    \$\begingroup\$ This is a case where I'd definitely use that latter style I showed in the previous answer, using relative weights. That way you can tune all of the values to where you want them, without fighting with the rebalancing logic as you make each change. I'd also strongly recommend moving to an array-based approach, so you don't have so much copy-paste code to maintain. \$\endgroup\$
    – DMGregory
    Mar 21 '18 at 11:13
  • \$\begingroup\$ Could you expand (explain more thoroughly) that logic here? It was pretty laconic so it is hard to catch. \$\endgroup\$ Mar 21 '18 at 11:30
1
\$\begingroup\$

First, I'd make a struct to package together your prefabs and weights, so you can store them in an array without having to juggle so many named variables to figure out which number goes with which prefab.

[System.Serializable]
public struct PrefabEntry {
    #if UNITY_EDITOR
    // This helps it look neater in the inspector.
    [HideInInspector]
    public string name;
    #endif
    public GameObject prefab;
    public float weight;
}

Next we set aside an array of these to use in-game. We'll maintain the invariant that these weights always add to 100.

[SerializeField]
[HideInInspector]
PrefabEntry[] _normalizedPrefabs;

But we hide that version, and instead show an Inspector-only version to the user:

#if UNITY_EDITOR
[SerializeField]
PrefabEntry[] _weightedPrefabs = new PrefabEntry[0];

void OnValidate() {
    // When the array is modified,
    // update all the entries' names,
    // and total up the weight accounted for.
    float totalWeight = 0f;

    for(int i = 0; i < _weightedPrefabs.Length; i++) {
        var entry = _weightedPrefabs[i];
        if(entry.prefab == null)
            continue;

        entry.name = entry.prefab.name;
        _weightedPrefabs[i] = entry;
        totalWeight += entry.weight;
    }

    if (totalWeight == 0f)
        return;

    float reweight = 100f / totalWeight;

    // Create/resize the normalized version.
    if(_normalizedPrefabs == null || _normalizedPrefabs.Length != _weightedPrefabs.Length)
        _normalizedPrefabs = new PrefabEntry [_weightedPrefabs.Length];

    // Fill this array with copies of the original,
    // reweighted to add up to 100
    for(int i = 0; i < _weightedPrefabs.Length; i++) {
        var entry = _weightedPrefabs[i];
        if(entry.prefab == null)
            entry.weight = 0f;

        entry.weight *= reweight;
        _normalizedPrefabs[i] = entry;
    }
}
#endif

Now every time you make a change, we ensure the version used in-game is adjusted to add up to 100 without changing the original values shown in the inspector.

So if you want to make a coordinated change like setting four prefabs to 25% each and the rest to 0, you can do that in one fell swoop without fighting the renormalizing logic that keeps trying to change your zeroes to1/remainingTotal ;)

You'll just need to be careful that all your scripts that consume this data use the _normalized version. If you want to enforce that, you could encapsulate this in a type with a public getter for only the _normalized array.

Here's what it looks like in the Inspector:

Inspector showing a list of named pairs of prefabs & numeric weights

One last note: usually for probability math, we'll normalize so the total is 1 instead of 100, but I've left it as 100 above to fit with what you're doing so far.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .