Search Unity

Manage UI elements

Discussion in 'UGUI & TextMesh Pro' started by VTheDrago0nS, Aug 6, 2019.

  1. VTheDrago0nS

    VTheDrago0nS

    Joined:
    Feb 5, 2018
    Posts:
    3
    Hello!
    I'm doing a simple UI for one of my projects and I've come to a dead end as to how I can manage opening and closing different UI elements.

    What I mean is, in my UI i have a couple of tabs lets say Main Menu, Options Menu, New Game Menu (and so on).

    My question is what is a good way of switching between these menus that does the least amount of load to the platform (im planing on running this on android so the least amount of memory the better).

    So far I have:

    Code (CSharp):
    1.  public void Options()
    2.     {
    3.         myObjects[0].SetActive(true);
    4.         myObjects[1].SetActive(false);
    5.         myPanels[2].SetActive(false);
    6.     }
    7.  
    8.     public void GoBackToMainMenu()
    9.     {
    10.         myPanels[3].SetActive(false);
    11.         myPanels[2].SetActive(false);
    12.         myPanels[1].SetActive(false);
    13.         myPanels[0].SetActive(true);
    14.      }
    15.  

    Basically I use panels to group all the different UI parts and those panels have buttons that call these methods and it will never brake because those buttons are only active on those panels. And its basically 1 line of code.

    But this looks. UUUUUUUUUUH. I can't think of a better way to do this is there a better way to do this? Thank you in advance!
     
  2. larsolm5853

    larsolm5853

    Joined:
    Oct 24, 2017
    Posts:
    21
    Setting VisualElements to be inactive will only prevent them from being interactive with, they will render and appear grayed out just like enabled and disabled fields in IMGUI.

    To remove them completely from the layout so they don't render or respond to interactions set their "display" property to "none" and back to "flex" when you want to re-enable them.

    Code (CSharp):
    1. public void Options()
    2. {
    3.     myObjects[0].style.display = DisplayStyle.Flex;
    4.     myObjects[1].style.display = DisplayStyle.None;
    5.     myPanels[2].style.display = DisplayStyle.None;
    6. }
    7. public void GoBackToMainMenu()
    8. {
    9.     myPanels[3].style.display = DisplayStyle.None;
    10.     myPanels[2].style.display = DisplayStyle.None;
    11.     myPanels[1].style.display = DisplayStyle.None;
    12.     myPanels[0].style.display = DisplayStyle.Flex;
    13. }
    Generally it better to do this type of thing with .uss style sheets but this is nice and simple.
     
  3. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    The easiest way would be a state machine. That way, each state turns itself on and off, so no need to turn off all the other windows. There are some free ones on the asset store or github. The idea would be something like this:

    Code (csharp):
    1. public class MainMenuState : State
    2. {
    3.     void OnStateEnter() { vars.MainMenu.SetActive(true); }
    4.     void OnStateExit() { vars.MainMenu.SetActive(false); }
    5. }
    6.  
    7. // Usage example
    8. guiStateMachine.SetState(mainMenuState);
    A dirty way here would just be to create a TurnEverythingOff() function and call it first.

    Also, this is UIElements forum, not the forum for in-game UI, if that's what this is for - https://forum.unity.com/forums/unity-ui-ugui-textmesh-pro.60/
     
    VTheDrago0nS likes this.
  4. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    789
    @VTheDrago0nS
    For your current case (different menus for your game) the statemachine thing suggested by @Stardog is probably the best solution.

    It is powerful, clean, and it leaves the door open for custom handling of state changes later on!

    However it might not be the most time-efficient thing you can do! :D
    A statemachine setup is more up-front work and more work to maintain.
    And if you're just making a small game, it might not be worth the time investment.

    Your initial approach can be improved a lot in just a few steps!

    First step:
    Lets deal with the 'boring' indexing into arrays.
    Code (CSharp):
    1. public void GoBackToMainMenu()
    2. {
    3.     myPanels[3].SetActive(false);
    4.     myPanels[2].SetActive(false);
    5.     myPanels[1].SetActive(false);
    6.     myPanels[0].SetActive(true);
    7. }
    Adding cases (lines) for each new panel will get boring quick, instead you could do:
    Code (CSharp):
    1. public void SwitchTo(int panelIndex)
    2. {
    3.     for(int i = 0; i < myPanels.Length; i++)
    4.         p.SetActive(panelIndex == i);
    5. }
    This is a more optimized variant of what stardog also suggested. (turning off all, then turning on the one you want).


    Next step:
    Addressing those panels will quickly become very hard, especially once you realize you might want to dynamically load more panels. Maybe plugins have their own panel? Or the user can open "windows" like an inventory or so...? So numbers are out.

    Maybe you could give each panel a name and then activate them by their name?
    Like
    SwitchTo("mainMenu");
    and inside the function
    p.SetActive(p.name == switchToName)
    instead of comparing the index...

    But strings can have typos, so that's still not good. So generics to the rescue!

    You most likely already have a sort of "controller" on each panel that wires up all the components with each other, so that one can be used to address the menu without any ambiguity (and autocomplete in vs!)
    Code (CSharp):
    1. interface IMenuPanel { } // marker interface
    2.  
    3. class MainMenuPanel : IMenuPanel { ... }
    4. class OptionsPanel : IMenuPanel { ... }
    5. ...
    Code (CSharp):
    1. public void SwitchTo<TMenu>() where T : IMenuPanel
    2. {
    3.     foreach(var p in myPanels)
    4.        p.SetActive(p.GetComponent<TMenu>() != null);
    5. }
    6.  
    7. // switch to main menu
    8. SwitchTo<MainMenu>();
    The generic constraint on the method (
    where T : IMenuPanel
    ) ensures that you can't accidentally use a type that isn't actually a menu panel.

    Next, composition:
    Nice, but we can tack on a few more minor things to make this much more powerful.

    1. Instead of manually populating an array or list of panels, we can now just do something like this
    IMenuPanel[] myPanels = FindObjectsOfType(typeof(IMenuPanel));


    2. In SwitchTo we should also return the component so that we can use it immediately.
    Code (CSharp):
    1. public TMenu SwitchTo<TMenu>() where T : IMenuPanel
    2. {
    3.     TMenu menuComponent = null;
    4.     foreach(var p in myPanels)
    5.     {
    6.         var c = p.GetComponent<TMenu>();
    7.         if(c != null)
    8.         {
    9.             menuComponent = c;
    10.             p.SetActive(true);
    11.         }
    12.         else
    13.         {
    14.             p.SetActive(false);
    15.         }
    16.     }
    17.     return menuComponent;
    18. }
    19.  
    20. // assuming your game has an options panel that has a few tabs
    21. // inside it for each category (video, audio, gameplay, ...):
    22. SwitchTo<OptionsPanel>()?.SelectTab(OptionTabs.Audio);
    3. From here we can build even more stuff ontop!
    Maybe some logging when the menu wasn't found? To improve startup time: maybe
    SwitchTo
    can instantiate each panel as it is requested, instead of having them all loaded from the start? ...
     
    ilEtik likes this.