Search Unity

Question How to create a slider like a scale

Discussion in 'Scripting' started by Philipp_Goettler, May 30, 2023.

  1. Philipp_Goettler

    Philipp_Goettler

    Joined:
    Dec 11, 2021
    Posts:
    28
    Hey, atm i am trying to create a units inventory menu. And now i wanna implement a feature when you drag and drop a unit from one inventory to another, a slider will open and on the left and right are the numbers from both inventory (already got this). Now i wanna add a feature like a scale, when i move my slider to the left, the left number increases and the right one decreases, same on the other side.
    [Edit]
    Here is also a example image

    upload_2023-5-30_17-19-16.png
     
    Last edited: May 30, 2023
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    I did this a while ago and keep an example handy! See enclosed package for it fully set up.

    Code:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class MutuallyLimitedSliders : MonoBehaviour
    7. {
    8.     [Tooltip( "Sliders will be in total limited to never exceed this.")]
    9.     public    float        MaximumTotal;
    10.  
    11.     [Tooltip( "Set this to reduce other sliders instead of limiting the moving one.")]
    12.     public    bool        ReduceOthers;
    13.  
    14.     [Header( "CAVEAT: only tested on 0.0 to 1.0 sliders!")]
    15.  
    16.     [Tooltip( "Sliders to observe and mutually limit.")]
    17.     public    Slider[]    Sliders;
    18.  
    19.     void Reset()
    20.     {
    21.         MaximumTotal = 1.0f;
    22.     }
    23.  
    24.     float LastMaximumTotal;
    25.     float[] LastSliderValues;
    26.  
    27.     void SetSliderGuarded( Slider slider, float value)
    28.     {
    29.         if (value < slider.minValue) value = slider.minValue;
    30.         if (value > slider.maxValue) value = slider.maxValue;
    31.         slider.value = value;
    32.     }
    33.  
    34.     void Update ()
    35.     {
    36.         // if you change the maximum, trigger the full restart checks
    37.         if (MaximumTotal != LastMaximumTotal)
    38.         {
    39.             LastSliderValues = null;
    40.             LastMaximumTotal = MaximumTotal;
    41.         }
    42.  
    43.         // is this our first time or did the count of sliders change?
    44.         if (LastSliderValues == null || LastSliderValues.Length != Sliders.Length)
    45.         {
    46.             LastSliderValues = new float[ Sliders.Length];
    47.  
    48.             float currentTotal = 0.0f;
    49.             for (int i = 0; i < Sliders.Length; i++)
    50.             {
    51.                 LastSliderValues[i] = Sliders[i].value;
    52.                 currentTotal += Sliders[i].value;
    53.             }
    54.  
    55.             if (currentTotal > MaximumTotal)
    56.             {
    57.                 Debug.LogWarning( "Total already greater than max at start!");
    58.  
    59.                 if (ReduceOthers)
    60.                 {
    61.                     for (int i = 0; i < Sliders.Length; i++)
    62.                     {
    63.                         float reducedValue = (Sliders[i].value * MaximumTotal) / currentTotal;
    64.                         SetSliderGuarded( Sliders[i], reducedValue);
    65.                     }
    66.                 }
    67.             }
    68.         }
    69.  
    70.         // check and limit
    71.         {
    72.             bool adjusted = false;
    73.             for (int i = 0; i < Sliders.Length; i++)
    74.             {
    75.                 if (Sliders[i].value != LastSliderValues[i])
    76.                 {
    77.                     if (!adjusted)
    78.                     {
    79.                         // tally others
    80.                         float othersTotal = 0.0f;
    81.                         for (int j = 0; j < Sliders.Length; j++)
    82.                         {
    83.                             if (i != j)
    84.                             {
    85.                                 othersTotal += Sliders[j].value;
    86.                             }
    87.                         }
    88.  
    89.                         // limit?
    90.                         if (othersTotal + Sliders[i].value > MaximumTotal)
    91.                         {
    92.                             adjusted = true;        // an adjustment has happend, don't do any other this frame
    93.  
    94.                             bool doLimiting = true;
    95.  
    96.                             if (ReduceOthers)
    97.                             {
    98.                                 // we will bring all the others down to fit, if possible
    99.                                 float overAmount = (othersTotal + Sliders[i].value) - MaximumTotal;
    100.  
    101.                                 // we need to reduce the others by overAmount... can we?
    102.                                 if (overAmount < othersTotal)
    103.                                 {
    104.                                     doLimiting = false;
    105.  
    106.                                     // proportionally reduce the others
    107.                                     for (int j = 0; j < Sliders.Length; j++)
    108.                                     {
    109.                                         if (i != j)
    110.                                         {
    111.                                             float reducedValue = Sliders[j].value - (Sliders[j].value * overAmount) / othersTotal;
    112.                                             SetSliderGuarded( Sliders[j], reducedValue);
    113.                                         }
    114.                                     }
    115.                                 }
    116.                                 else
    117.                                 {
    118.                                     // we cannot... therefore we must limit this one
    119.                                     doLimiting = true;
    120.                                     // meanwhile drive all the others to their minvaule and retotal
    121.                                     othersTotal = 0.0f;
    122.                                     for (int j = 0; j < Sliders.Length; j++)
    123.                                     {
    124.                                         if (i != j)
    125.                                         {
    126.                                             Sliders[j].value = Sliders[j].minValue;
    127.                                             othersTotal += Sliders[j].value;
    128.                                         }
    129.                                     }
    130.                                 }
    131.                             }
    132.  
    133.                             // we either ar not reducing, OR we were unable to reduce, so fall back to limit
    134.                             if (doLimiting)
    135.                             {
    136.                                 // we will prevent this slider from causing the total to exceed
    137.                                 float limited = MaximumTotal - othersTotal;
    138.                                 SetSliderGuarded( Sliders[i], limited);
    139.                             }
    140.                         }
    141.                     }
    142.  
    143.                     LastSliderValues[i] = Sliders[i].value;
    144.                 }
    145.             }
    146.         }
    147.     }
    148. }
     

    Attached Files:

    Philipp_Goettler likes this.
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,105
    First of all, Kurt's answer above has you covered, this isn't intended to replace it.
    But, I can walk you through the math and logic behind this.

    First of all you have some value t.
    Such that 0 <= t <= 1.

    Next, you want two points A and B that will graphically pin the two extrema of the slider.
    Theoretically, you can find any point in between A and B if you apply linear interpolation or lerp.
    Code (csharp):
    1. static Vector3 lerp(Vector3 a, Vector3 b, float t) => (1f - t) * a + t * b;
    (Alternatively you can use Vector3.Lerp)
    To use this you use a value
    t
    between 0 and 1, imagine if this was percentage.

    Knowing t allows you to determine the values on the left and on the right. Let's call these values q1 and q2.

    So when t is at 0% then q1 = total (or total * 1.0)
    when t is at 100% then q1 = 0 (or total * 0.0)
    and when t is at 50% then q1 = total / 2 (or total * 0.5)

    Note that the following equation must be satisfied: q1 + q2 = total
    Meaning that you can compute q2, and get q1 as total - q2.

    This means there is a similar relationship like that lerp above, just with numbers
    Code (csharp):
    1. static float lerp(float a, float b, float t) => (1f - t) * a + t * b;
    And then
    Code (csharp):
    1. var q2 = lerp(0f, total, t);
    2. var q1 = total - q2;
    In the end, what you want is really this
    t
    , so how to find this from the set points?
    If you know A and B and have some P on this line, you can do inverse lerp.

    Ordinary inverse lerp for numbers is just reversing the straight lerp. I.e. you take the original formula and try to work out what
    t
    should be equal to:
    Code (csharp):
    1. n = (1f - t) * a + t * b
    2. n = a - ta + tb
    3. n = a + tb - ta
    4. n = a + t(b - a)
    5. t(b - a) = n - a
    6. t = (n - a) / (b - a)
    let's just assume that a and b won't be the same number, and this will work
    Code (csharp):
    1. static float invLerp(float a, float b, float n) => (n - a) / (b - a);
    Ok but now you need this for points A and B, and it's slightly more complicated because now you have 3 sets of values. However, because all three points basically all sit on one line, you don't actually need all 3 sets, you just pick the values that seem to be the most representative, say for horizontal axis that would be X coordinates.

    So you could do
    Code (csharp):
    1. static float invLerp(Vector3 a, Vector3 b, Vector3 n) => (n.x - a.x) / (b.x - a.x);
    And now you have everything you need
    Code (csharp):
    1. float qty = 20f; // for example
    2. float t = invLerp(minPoint, maxPoint, valuePoint);
    3. float q2 = lerp(0f, qty, t);
    4. float q1 = qty - q2;
    But you probably don't want these values to be floating points, but integers. You can do
    Code (csharp):
    1. int qty = 20;
    2. var t = invLerp(minPoint, maxPoint, valuePoint);
    3. int q2 = (int)Mathf.Round(lerp(0f, (float)qty, t));
    4. int q1 = qty - q2;
    You can similarly manipulate the actual slider to make it snap to whole numbers etc.

    Anyway, if this is perhaps too complicated for you -and- your slider already computes the value
    t
    for you, then simply do
    Code (csharp):
    1. int qty = 20;
    2. var t = slider.value; // or whatever is the name of this property
    3. int q2 = (int)Mathf.Round(lerp(0f, (float)qty, t));
    4. int q1 = qty - q2;
    You can also scale slider.value appropriately. For example if you've made it so it goes from 0..50, then
    Code (csharp):
    1. int qty = 20;
    2. var t = slider.value / slider.maxValue; // divide by 50
    3. int q2 = (int)Mathf.Round(lerp(0f, (float)qty, t));
    4. int q1 = qty - q2;
     
    Philipp_Goettler likes this.