Search Unity

Help with limiting toggle selection to 3

Discussion in 'Scripting' started by LightOwl, Aug 22, 2018.

  1. LightOwl

    LightOwl

    Joined:
    Mar 28, 2017
    Posts:
    15
    I am trying to create a list of toggles from which the user can select at most 3 items at which point the rest of the toggles are disabled.

    Note: From what I read, ToggleGroup is not appropriate for this since is limits toggle selection to 1, so everyone suggested writing code.

    In pseudo code my code looks like:

    Initialize Count variable
    Public array of toggles (6)
    Public toggle GOs (6, yes, the same ones as above, explanation later…)

    Start

    Set count to zero
    Use Enable function to make sure all toggles are enabled (they should already be enabled, so this may just be fluff)

    Update

    Here I have listeners for all 6 toggles. Here is the syntax I am using:
    ThisIsTheToggleGO.GetComponent().onValueChanged.AddListener((isOn) => { ActiveToggle(); });

    ActiveToggle function

    Goes through the array of toggles counting the ones which are selected

    If the count is greater than 3 then it goes to the Disable function
    else it goes to Enable function

    Disable function (make toggle not interactable)
    This goes through all the toggle gameobjects (one if statement for each toggle) and disables those that are not selected

    Enable function (make toggle interactable)
    This goes through all the toggle gameobjects (one line for each toggle) and enables the toggle

    This almost works, except, the count is not a unique count, so when I select a single toggle, all the rest are disabled . If I put the listeners in Start, they don’t do anything although that is where the manual shows to put listeners. So because the listeners are in update the activetoggle count function is counting the same toggle every update. I then tried to set the count back to zero at both the disable/enable functions but then the count remained at perpetual zero.

    I have several questions regarding this:

    1. My understanding is that the listener for a toggle is ‘listening’ for when the toggle is selected at which point I can have it take an action as a result of the select. Is this correct? If so, Where should the listener go if the end user is actively selecting and deselecting toggles. In Start, Update, it’s own function? Is there a better ‘tool’ choice than this? Like OnSelect? if so, why is it better? how the heck do I use it. the manual is not helpful with this. This is a which tool is best for the job question.

    2. Would it be possible to do all of this with a single Array? as opposed to using GameObjects in addition to the array. If so, can you point me in the direction in how to do this? I tried but wasn’t able to implement since I have extremely limited array experience - most lessons focus on using the GOs directly. Another which tool is best for the job question.

    3. How can I get a unique count of the toggles. This may be resolved by either removing the listeners from Update or just using GO’s and ditching the array, which I may do, but first… Is there any way to fix what I have already written to get a unique count?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    I would do something like this:

    Put all the toggle GameObjects parented under another GameObject. I'll call that one "Top" and that is where you put your "OnlyThreeAllowed" script.

    Since this is simple UI, we can all agree performance isn't a huge concern. Therefore, in your Update() loop to keep it nice and simple, do the following:

    Code (csharp):
    1.     void Update ()
    2.     {
    3.         Toggle[] toggles = GetComponentsInChildren<Toggle>();
    4.  
    5.         int maxCount = 3;
    6.         int count = 0;
    7.  
    8.         for (int i = 0; i < toggles.Length; i++)
    9.         {
    10.             if (toggles[i].isOn)
    11.             {
    12.                 count++;
    13.                 if (count > maxCount)
    14.                 {
    15.                     toggles[i].isOn = false;
    16.                 }
    17.             }
    18.         }
    19.     }
    Your alternate choice is to fiddle with listeners and callbacks and all manner of fragile stuff that will ultimately be slightly more performant, but will also be far more subject to future failure, silent failure, and complicated side effects of listeners being added and removed.

    Unitypackage enclosed containing the above codelet and scene.

    When in doubt, use Update(), check it every frame and don't trust nobody to add/remove themselves from your listeners. :)

    Edit: oops, realized I had a weird shaped screen when I saved that package so the toggles might be way off to the lower left corner. Select one and then hover over the Scene with your cursor and press F to find it.
     

    Attached Files:

    RetroBox and LightOwl like this.
  3. LightOwl

    LightOwl

    Joined:
    Mar 28, 2017
    Posts:
    15
    Thank you Kurt, that is so much cleaner!
     
    Kurt-Dekker likes this.
  4. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    1. You know how you can select a Button in the Unity inspector and tell it to call specific functions when the button is clicked? This is like that, except you're telling it the function to call in code, instead of in the editor. The OnValueChange event on a Toggle is called both when the toggle is selected and when it is de-selected.

    Once you add a listener, that listener will remain in effect forever unless you specifically remove it. You should DEFINITELY NOT add a listener every frame in update. If you add the same listener twice, then that function gets called twice every time the button changes. If you add the same listener ten times, then that function gets called ten times every time the button changes, etc.

    2. If you have an array of Toggles, you can get the corresponding GameObject of each by saying (toggle).gameObject. If you have the GameObjects, you can get the Toggles by calling (gameObject).GetComponent<Toggle>().

    (This assumes that the GameObject you want is the one that the Toggle is attached to. If they're different, you may need to do something more complex.)

    3.
    Code (CSharp):
    1. int count = 0;
    2. foreach (Toggle tog in toggles)
    3. {
    4.     if (tog.isOn) ++count;
    5. }