Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Vosheck · Sep 15, 2018 at 04:50 PM · uisliderinputfield

[UI] How to best set up connected/dependent sliders(or other user input oriented UI elements)?

What would be the best way to set up and X number of code instantiated sliders that add up to a shared total? E.g. Slider A is on 100%, Slider B and C are on 0%. Moving Slider A to 90% would result in Sliders B and C increasing to 5% each.

Simply adding a listener onValueChanged and controling the slider.value through code doesn't quite work because any change of a slider's value invokes onValueChanged in an infinite loop.

I thought about using IPointerUpHandler and IPointerDownHandler to subscribe/unsubscribe listeners to onValueChanged in order to prevent the infinite loop, but it seems kinda hacky. E.g. onValueChanged listeners are subscribed-->user modifies a slider by clicking on it-->OnPointerDown unsubscribes all the onValueChanged listeners of sliders except the slider that has been clicked-->OnPointerUp subscribes the listeners of the aforementioned sliders.

Using IpointerUpHandler to determine which slider has been modified by detecting which UI element is under the mouse seems finicky at best.

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

1 Reply

· Add your reply
  • Sort: 
avatar image
0
Best Answer

Answer by Vosheck · Jul 07, 2020 at 08:29 AM

I eventually made a script for this:

 using System;
 using UnityEngine.Events;
 using UnityEngine;
 using UnityEngine.UI;
 using System.Collections.Generic;
 
 public class DependentUIElementManager : MonoBehaviour {
     //NOTICE:UnityEventBase.RemoveAllListeners() will disrupt completely the functioning of this script. Please use 
     //RemoveListener() instead. There is an example of RemoveListener()'s use below.
 
     #region Variables
     //The combined max value of all the dependent sliders and/or input fields
     [SerializeField]
     float sharedTotal = 1f;
     //If both sliders and input fields are used, then the correspondent UI element indices should be the same.
     //E.g. The value in the 0th slidercorresponds to the value of the 0th inputField
     [SerializeField]
     Slider[] sliders;
     [SerializeField]
     InputField[] inputFields;
 
     //Cached Actions for RemoveListener(), in order to prevent an infinite loop
     UnityAction<float> onValueChangedListener;
     UnityAction<string> onStringChangedListener;
 
     private int indexOfTheUIElementBeingModified;
     private bool allowIndexModification=true;
     private List<int> orderOfLastModifiedSliders;
     #endregion
 
     #region Initialization
     /// <summary>
     /// Initializes the UnityActions and sets the starting values.
     /// </summary>
     /// <param name="initialValues"></param>
     public void InitializeSetValues (float [] initialValues) {
         onValueChangedListener += ModifyTheValuesOfAllDependentSliders;
         onStringChangedListener += ModifyTheStringsOfAllDependentInputFields;
         for (int i = 0; i < sliders.Length; i++) {
             sliders[i].value = initialValues[i];
             //Contemporance
             int j = i;
             sliders[i].onValueChanged.AddListener((value) => { SetTheIndexOfTheUIElementBeingModified(j); });
             sliders[i].onValueChanged.AddListener(onValueChangedListener);
         }
         for (int i = 0; i < inputFields.Length; i++) {
             inputFields[i].text = initialValues[i].ToString();
             //Contemporance
             int j = i;
             inputFields[i].onValueChanged.AddListener((value) => { SetTheIndexOfTheUIElementBeingModified(j); });
             inputFields[i].onValueChanged.AddListener(onStringChangedListener);
         }
         orderOfLastModifiedSliders = new List<int>();
         for (int i = 0; i < sliders.Length; i++) {
             orderOfLastModifiedSliders.Add(i);
         }
         orderOfLastModifiedSliders.Reverse();
     }
 
     [ContextMenu("Initialize Evenly Distributed")]
     public void InitializeEvenlyDistributedValues() {
         onValueChangedListener += ModifyTheValuesOfAllDependentSliders;
         onStringChangedListener += ModifyTheStringsOfAllDependentInputFields;
         for (int i = 0; i < sliders.Length; i++) {
             sliders[i].value = sharedTotal / sliders.Length;
             //Contemporance
             int j = i;
             sliders[i].onValueChanged.AddListener((value) => { SetTheIndexOfTheUIElementBeingModified(j); });
             sliders[i].onValueChanged.AddListener(onValueChangedListener);
         }
         for (int i = 0; i < inputFields.Length; i++) {
             inputFields[i].text = (sharedTotal/inputFields.Length).ToString();
             //Contemporance
             int j = i;
             inputFields[i].onValueChanged.AddListener((value) => { SetTheIndexOfTheUIElementBeingModified(j); });
             inputFields[i].onValueChanged.AddListener(onStringChangedListener);
         }
     }
 
 
     private void CheckValidityOfInitialValues(float[] initialValues) {
         if (sliders.Length != 0 && initialValues.Length != sliders.Length)
             Debug.LogError("Error in initializing the initial values of dependent sliders. InitialValues length is: " + initialValues.Length +
                 " ,while the slider's array length is: " + sliders.Length);
         else if (inputFields.Length != 0 && initialValues.Length != inputFields.Length)
             Debug.LogError("Error in initializing the initial values of dependent inputFields. InitialValues length is: " + initialValues.Length +
                 " ,while the inputFields's array length is: " + inputFields.Length);
         float initialValuesSum = 0f;
         for (int i = 0; i < initialValues.Length; i++) {
             initialValuesSum += initialValues[i];
         }
         if (initialValuesSum != sharedTotal)
             Debug.LogError("Value mismatch in the initial values provided and the shared total of the dependent sliders. Initial value sum is: "+
                 initialValuesSum+" ,while the shared total is: "+sharedTotal);
     }
 
     public void ReinitializeArrays() {
         sliders = new Slider[0];
         inputFields = new InputField[0];
     }
 
     #endregion
 
     #region Methods for adding sliders/input fields into the arrays
     /// <summary>
     /// Appends a slider to the array that holds the sliders whose values are interdependent
     /// </summary>
     /// <param name="slider"></param>
     public void AddToDependentSliders(Slider slider) {
         Array.Resize(ref sliders, sliders.Length + 1);
         sliders[sliders.Length - 1] = slider;
     }
     /// <summary>
     /// Appends an array of sliders to the array that holds the sliders whose values are interdependent
     /// </summary>
     /// <param name="slidersToAdd"></param>
     public void AddToDependentSliders(Slider[] slidersToAdd) {
         int initialLength = sliders.Length;
         Array.Resize(ref sliders, sliders.Length + slidersToAdd.Length);
         slidersToAdd.CopyTo(sliders, initialLength);
     }
 
     /// <summary>
     /// Appends an input field to the array that holds the input fields whose values are interdependent
     /// </summary>
     /// <param name="inputField"></param>
     public void AddToDependentInputField(InputField inputField) {
         Array.Resize(ref inputFields, inputFields.Length + 1);
         inputFields[inputFields.Length - 1] = inputField;
     }
     /// <summary>
     /// Appends an array of input fields to the array that holds the input fields whose values are interdependent
     /// </summary>
     /// <param name="inputFieldsToAdd"></param>
     public void AddToDependentInputField(InputField[] inputFieldsToAdd) {
         int initialLength = inputFields.Length;
         Array.Resize(ref inputFields, inputFields.Length + inputFieldsToAdd.Length);
         inputFieldsToAdd.CopyTo(inputFields, initialLength);
     }
     #endregion
 
     #region Listeners
     /// <summary>
     /// The listener method that controls the distribution of the total value throughout the dependent sliders
     /// </summary>
     /// <param name="newValue"></param>
     public void ModifyTheValuesOfAllDependentSliders(float newValue) {
         /*allowIndexModification = false;
         float clampedValue = Mathf.Clamp(newValue,sliders[indexOfTheUIElementBeingModified].minValue, sliders[indexOfTheUIElementBeingModified].maxValue);
         float valueOfAllDependentSliders = (sharedTotal - clampedValue) / (sliders.Length-1);
         for (int i = 0; i < sliders.Length; i++) {
             //For all sliders except the one being currently modified by the player
             if (i!=indexOfTheUIElementBeingModified) {
                 //Removing the listener is important to prevent an infinite loop of onValueChanged
                 sliders[i].onValueChanged.RemoveListener(onValueChangedListener);
                 sliders[i].value = valueOfAllDependentSliders;
                 sliders[i].onValueChanged.AddListener(onValueChangedListener);
             }
         }
         if (ShouldEqualizeSliderAndInputField())
             EqualizeInputFieldsToSliders();
 
         allowIndexModification = true;*/
         allowIndexModification = false;
         float clampedValue = Mathf.Clamp(newValue, sliders[indexOfTheUIElementBeingModified].minValue, sliders[indexOfTheUIElementBeingModified].maxValue);
         float valueOfAllDependentSliders = (sharedTotal - clampedValue) / (sliders.Length - 1);
         orderOfLastModifiedSliders.Remove(indexOfTheUIElementBeingModified);
         orderOfLastModifiedSliders.Add(indexOfTheUIElementBeingModified);
         float valueToRedistribute =sharedTotal - clampedValue - sumValueOfSlidersNotBeingModified();
         //Debug.Log("value to redistribute: " + valueToRedistribute + "clamped value: " + clampedValue + "sum Of Not modified: " + sumValueOfSlidersNotBeingModified());
         for (int i = 0; i < sliders.Length; i++) {
             if (valueToRedistribute != 0) {
                 int indexToModify = orderOfLastModifiedSliders[i];
                 if (indexToModify != indexOfTheUIElementBeingModified) {
                     float ableToRedistributeToThisSlider =valueToRedistribute>0 ? Mathf.Min(sliders[indexToModify].maxValue-sliders[indexToModify].value,valueToRedistribute):
                         Mathf.Max(sliders[indexToModify].minValue-sliders[indexToModify].value,valueToRedistribute);
                     //Debug.Log("able to redistribute to slider " + indexToModify + " " + ableToRedistributeToThisSlider);
                     valueToRedistribute -= ableToRedistributeToThisSlider;
                     sliders[indexToModify].onValueChanged.RemoveListener(onValueChangedListener);
                     sliders[indexToModify].value += ableToRedistributeToThisSlider;
                     sliders[indexToModify].onValueChanged.AddListener(onValueChangedListener);
                 }
             }
         }
         if (ShouldEqualizeSliderAndInputField())
             EqualizeInputFieldsToSliders();
 
         allowIndexModification = true;
 
     }
 
     float sumValueOfSlidersNotBeingModified() {
         float cumulus = 0f;
         for (int i = 0; i < sliders.Length; i++) {
             if (i != indexOfTheUIElementBeingModified)
                 cumulus += sliders[i].value;
         }
         return cumulus;
     }
 
     /// <summary>
     /// The listener method that controls the distribution of the total value throughout the dependent input fields
     /// </summary>
     /// <param name="newValue"></param>
     public void ModifyTheStringsOfAllDependentInputFields(string newValue) {
         if (!string.IsNullOrEmpty(newValue)) {
             allowIndexModification = false;
             float parsedValue = 0f;
             float.TryParse(newValue, out parsedValue);
             float clampedValue = Mathf.Clamp(parsedValue, sliders[indexOfTheUIElementBeingModified].minValue, sliders[indexOfTheUIElementBeingModified].maxValue);
             float valueOfAllDependentInputFields = (sharedTotal - clampedValue) / (inputFields.Length - 1);
             for (int i = 0; i < inputFields.Length; i++) {
                 //For all input fields except the one being currently modified by the player
                 if (i != indexOfTheUIElementBeingModified) {
                     //Removing the listener is important to prevent an infinite loop of onValueChanged
                     inputFields[i].onValueChanged.RemoveListener(onStringChangedListener);
                     inputFields[i].text = valueOfAllDependentInputFields.ToString();
                     inputFields[i].onValueChanged.AddListener(onStringChangedListener);
                 }
             }
             if (ShouldEqualizeSliderAndInputField())
                 EqualizeSlidersToInputFields();
             allowIndexModification = true;
         }
     }
 
     /// <summary>
     /// A simple check to find out if only sliders/ input fields are used, or both
     /// </summary>
     /// <returns></returns>
     private bool ShouldEqualizeSliderAndInputField() {
         if (sliders.Length == 0 || inputFields.Length == 0)
             return false;
         else if (sliders.Length != inputFields.Length) {
             Debug.LogError("There are inequal amount of dependent sliders and input fields.");
             return false;
         }
         else
             return true;
     }
 
     /// <summary>
     /// Sets the Input fields' texts to correspond to the slider values
     /// </summary>
     private void EqualizeInputFieldsToSliders() {
         for (int i = 0; i < inputFields.Length; i++) {
             inputFields[i].onValueChanged.RemoveListener(onStringChangedListener);
             inputFields[i].text = sliders[i].value.ToString();
             inputFields[i].onValueChanged.AddListener(onStringChangedListener);
         }
     }
     /// <summary>
     /// Sets the Sliders' values to correspond to the input field texts
     /// </summary>
     private void EqualizeSlidersToInputFields() {
         for (int i = 0; i < sliders.Length; i++) {
             sliders[i].onValueChanged.RemoveListener(onValueChangedListener);
             sliders[i].value = float.Parse(inputFields[i].text);
             sliders[i].onValueChanged.AddListener(onValueChangedListener);
         }
     }
 
     /// <summary>
     /// Method that determines the slider/Input field being modified by the player
     /// </summary>
     /// <param name="newIndex"></param>
     public void SetTheIndexOfTheUIElementBeingModified(int newIndex) {
         if(allowIndexModification)
             indexOfTheUIElementBeingModified = newIndex;
     }
     #endregion
 }
 
Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

152 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

UI slider "End slide" event 3 Answers

Unity InputField UI Not Responsive 0 Answers

Dynamic slider size with the new UI 2 Answers

left end of slider fill flattening the more i move the value down 0 Answers

Force uppercase in Input Field without using onValueChange? 3 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges