itsgalsugarfree
itsgalsugarfree

Reputation: 23

Unity Keep Selected Color on after press even if other buttons are pressed

I know this is a very long question, but I tried my best to be as clear as possible with this weird problem. Any help or suggestions would be appreciated. Let me know if I can provide any more resources.

The Setup

I am trying to create a sort-of navigation system where the user can still see what path they are on. I have three sets of buttons:

  1. Year Buttons - these are three static buttons that are always on the screen (year 1, 2 and 3).
  2. Discipline Buttons - these are dynamically instantiated and destroyed based on which year button has been selected.
  3. Module Buttons - these function the same as the discipline buttons, but take into account the year AND discipline button when instantiating and destroying.

How It Should Work

When the user clicks on a button, the button must change color and stay that color until a different button of THE SAME SET (mentioned above) is pressed.

Example

  1. User presses "YEAR 1" button. Button changes from orange to green. Discipline instances are instantiated based on year = 1.
  2. User then presses one of the instantiated DISCIPLINE buttons. This button changes from orange to green. The YEAR button should still stay green. Module instances are instantiated based on year=1 and discipline text.
  3. User presses "YEAR 2" button. module and discipline instances are destroyed, and YEAR 1 button must change back to original color. YEAR 2 button must change to green and discipline instances are instantiated.

My Code

I have three lists: one for buttons, one for disciplines, one for modules. The button list is static, while the other two are destroyed and added to as needed (the list adding/clearing does work). When I instantiate disciplines or modules, the previous ones are all destroyed (and the list is cleared) and then each instance is added to the list. Each instance has an onClick listener (made in the script, it does not work to make the listener Editor-side) which calls this general method:

public void ChangeAllWhite(List<GameObject> list)
    {
        foreach (var button in list)
        {
            button.GetComponent<Image>().color = orangeColor;
        }
    }

and then calls one of these three methods to change that instance color: public void YearColorChange(GameObject buttonToChange) { ChangeAllWhite(yearButtons); buttonToChange.GetComponent().color = selectedColor; }

public void DisciplineColorChange(GameObject buttonToChange)
{
    ChangeAllWhite(disciplineButtons);
    buttonToChange.GetComponent<Image>().color = selectedColor;
}

public void ModuleColorChange(GameObject buttonToChange)
{
    ChangeAllWhite(moduleButtons);
    buttonToChange.GetComponent<Image>().color = selectedColor;
}

This is the code to instantiate the disciplines (same as module pretty much) after the previous ones have been destroyed:

foreach (string item in disciplines)
        {
            GameObject disciplineInstance = Instantiate(DisciplineItem);
            disciplineInstance.transform.SetParent(DisciplineLayoutGroup.transform, false);
            disciplineInstance.GetComponentInChildren<Text>().text = item;
            disciplineButtons.Add(disciplineInstance);
            disciplineInstance.GetComponent<Button>().onClick.AddListener(() =>
            {
                UnityEngine.Debug.Log("debug discipline");
                AppManager.instance.classroomInfo.SetDiscipline(item);
                StartCoroutine(AppManager.instance.web.GetModules(AppManager.instance.classroomInfo.year, item));
                DisciplineColorChange(disciplineInstance);
                //needs year, discipline
            });
        } 

What currently happens

I don't get any errors, but the colors don't change. When I called the methods from an OnClick on the editor-side, it breaks and/or doesn't work. I think what is happening is that OnClick methods from previously instantiated (and now deleted) instances are trying to do something and then it just doesn't do anything. Any suggestions are welcome!

Upvotes: 0

Views: 6079

Answers (1)

Fattie
Fattie

Reputation: 12373

Your code seems incredibly over-complicated.

  1. Why not use the various button-states-colors that are built in to Button ?

enter image description here

Alternately, you must build a class that has the colors (animations, images, fonts, whatever) you want as a property and add that component to your buttons and use that. (In an ECS system, "adding a component" like that is a bit like extending a class in normal OO programming; you're just adding more behavior to the Button.)

So you would have your own cool

`SugarfreeButton`

and then you can do stuff like

   sugarfreeButton.Look = YourLooks.Possible;
   sugarfreeButton.Look = YourLooks.CannotChoose;
   sugarfreeButton.Look = YourLooks.Neon;

etc. You really HAVE to do this, you can't be dicking with setting colors etc in your higher-level code.

It's only a few lines of code to achieve this.

I've attached a completely random example of such "behaviors you might add to a button" at the bottom of this - DippyButton

  1. When you do "panels of buttons" it is almost a certainty that you use a layout group (HorizontalLayoutGroup or the vertical one). Do everything flawlessly in storyboard, and then just drop them inside that in the code with absolutley no positioning etc. concerns in the code.

Unfortunately, u won't be able to do anything in Unity UI until extremely familiar with the two layout groups (and indeed the subtle issues of LayoutElement, the fitters, etc.).

  1. You should almost certainly be using prefabs for the various button types. All of your code inside the "foreach" (where you add listeners and so on) is really not good / extremely not good. :) All of that should be "inside" that style of button. So you will have a prefab for your "discipline buttons" thing, with of course class(es) as part of that prefab which "do everything". Everything in Unity should be agent based - each "thing" should take care of itself and have its own intelligence. Here's a typical such prefab:

enter image description here

So these "lobby heads" (whatever the hell that is!) entirely take care of themselves.

In your example, when one is instantiated, on its own, it would find out what the currently selected "year" is. Does it make sense? They should look in the discipline database (or whatever) and decide on their own what their value should be (perhaps based on their position (easy - sibling index) in the layout group, or whatever is relevant.

You will never be able to engineer and debug your code the way you are doing it, since it is "upside down". In unity all game objects / components should "look after themselves".

  1. You should surely be using a toggle group as part of the solution, as user @derHugo says. In general, anything @derHugo says, I do, and you should also :)

Random code examples from point (1) ... DippyButton

using UnityEngine;
using UnityEngine.UI;

// simply make a button go away for awhile after being clicked

public class DippyButton : MonoBehaviour
{
    Button b;
    CanvasGroup fader;

    void Start()
    {
        b = GetComponent<Button>();
        if (b == null)
        {
            Destroy(this);
            return;
        }
        b.onClick.AddListener(Dip);
        
        fader = GetComponent<CanvasGroup>();
        // generally in Unity "if there's something you'll want to fade,
        // naturally you add a CanvasGroup"
    }

    public void Dip()
    {
        b.interactable = false;
        if (fader != null) { fader.alpha = 0.5f;
        }
        Invoke("_undip", 5f);
    }

    public void _undip()
    {
        b.interactable = true;
        if (fader != null) { fader.alpha = 1f; }
    }
}

Upvotes: 0

Related Questions