Best way managing panels

Hey all, I just wanted to get opinions how to manage panels cleanly. Just like in many games, there are a lot of panels to move around to from the lobby/menu. For mine, I have shop, characters, game modes, login panels (theres a lot), matchmaking etc. Instead of the usual, GameObject.SetActive(true/false); for like 100 panels, what is the best way to manage this.
I’ll appreciate all responses and thanks in advance.

I would put each panel in a separate Scene with a separate Canvas, then just additively load the ones you want, unload them when you no longer need them.

This lets you put all the junk related to that panel in one scene, including animating in, out, sparkles, etc.

Alternately, make each one a prefab and make a factory script that knows how to Instantiate it into wherever it needs to be, and how to remove it.

The only thing you need to handle is stacking / layering, and that can easily be taken care of:

Three (3) ways that Unity draws / stacks / sorts / layers / overlays stuff:

In short,

  1. The default 3D Renderers draw stuff according to Z depth - distance from camera.

  2. SpriteRenderers draw according to their Sorting Layer and Sorting Depth properties

  3. UI Canvas Renderers draw in linear transform sequence, like a stack of papers

If you find that you need to mix and match items using these different ways of rendering, and have them appear in ways they are not initially designed for, you need to:

  • identify what you are using
  • search online for the combination of things you are doing and how to to achieve what you want.

There may be more than one solution to try.

Additional reading in the official docs:

And SortingGroups can also be extremely helpful in certain circumstances:

Additive scene loading stuff:

A multi-scene loader thingy:

My typical Scene Loader:

Other notes on additive scene loading:

Timing of scene loading:

Also, if something exists only in one scene, DO NOT MAKE A PREFAB out of it. It’s a waste of time and needlessly splits your work between two files, the prefab and the scene, leading to many possible errors and edge cases.

Two similar examples of checking if everything is ready to go:

Hey Kurt
Thanks for this!!
Couple things

If I do this, I would be making a scene for all these panels,
8406621--1110390--upload_2022-9-1_10-53-27.png
But then for every component im setting active and closing, we’ll be back at the same spot, for example, for the login panel. 8406621--1110393--upload_2022-9-1_10-57-19.png
and within the popups, theres a s**t ton of buttons activating and closing things, and same for the other panels, lets say for my game mode panel, when I have opened that, theres buttons where you can open info on each game mode etc.
One thing I thought of is making a list of everything, attaching it to all necessary objects, but again

  1. That’s gonna take a hella long time for each object
  2. It will be clean and won’t be 100s of lines of code of Set.Active but continuing to add to that list for each thing I add it’s not gonna be dependable in my opinion

Hope I make sense because after reading that, I dont even understand what I’m tryna say

UI often ends up as a bit of a tangle. Though I find UI Toolkit makes this a lot better as you can directly codify your visual elements (by literally deriving from UnityEngine.UIElements.VisualElement or other existing classes) and embedding functionality rather than using components that work alongside the uGUI components.

I would take a look at what common factors each component of your UI has and make common interfaces for these. Remember, you can break down each bit of functionality into separate components to be responsible for their own affairs.

I agree with Kurt, too. With uGUI it’s a lot easier to break UI down into scenes. It certainly makes editing them easier, and stops them being directly dependant on other UI panels. This is less of a concern with UI Toolkit as your UI no longer lives in the scene.

What I left out of the above is that personally I hate writing custom UI hookups… I made my Datasacks package to do all the back and forth with UI objects with a bunch of generic scripts, leaving me free to just write the controller to interoperate with the datasacks.

This is a systems overview:

Datasacks is presently hosted at these locations:

https://bitbucket.org/kurtdekker/datasacks

The example “clicker” Game scene has an additively loaded scene popup called Popup1. You can see how simply it is all hooked up.

1 Like

I am making a game only in UI right now and have been thinking about panel management. This is a work in progress so open for discussion.
I made a base class called Window with basic functions like Show() and Close() and some abstracts like Init() for derived classes to override.
Then for every panel like the login I make a class derived from Window → LoginWindow.
Then I have a UIManager as a singleton that grabs all Windows in the scene in Start() and puts them in a Dictionary<type,window>.

Then whenever I need a special window I call UIManager.Instance.GetWindow().Show()
Everything related to that window like buttons and functions the window executes go in the script for that window. Some Windows need a bit of configuration for example a generic MessageWindow that displays a message to the player. For those there are config methods I use before showing the window:

GetWindow<MessageWindow>().WithMessage("Hello World!").Show()

There are also some events involved for example each Window has onWindowClosed. That way I can animate the closing window with a coroutine and when it is finished invoke the event. With that I am able to close a window and open the next one when the animation is done:

GetWindow<MessageWindow>()
  .WithMessage("Hello player!")
  .WithClosedAction(()=>DoSomething())
  .Show();

I have also used the idea with additive scene loading but that way I can’t configure the windows nicely and testing is a bit more complicated, also I had a problem accessing data from the main scene (not sure anymore what it was exactly)

I think the way around this then would be to have UI panels ‘subscribe’ to this manager on Awake/Start, and unsubscribe with OnDestroy. That way the manager doesn’t really care about them, it just sits there in the background and acts as a facilitator for other UI panels to find their brothers.

But a UI manager is a good idea to have in general. I’ve used one in a project that kept tabs in a manner like I mentioned. It also handled automatic ordering of the canvases (so that the newest one was always on top), and provided a common interface for closing the panel too (ESC by default, but could be overriden to a specific input in each panel) meaning I didn’t have to ‘code’ a way to close each panel.

Most of this was done with additive scene loading. This was all with uGUI. With UI Toolkit I’m finding my structure is a lot different.

1 Like

UI manglers (managers) are always a large part of the engineering work in any application because they change so much and their behavior is critical to the app functioning the way you want it.

Two common approaches are:

  • have a single central UI mangler that knows EVERYTHING, and you just tell it “go to inventory” and it takes care of getting you there, hiding or removing anything else that might have been present. This is more code, but more central control.

  • have every part and panel of your UI only responsible for appearing and going away when told, and optionally accepting a “go here next” delegate, and then chaining everything together. This is less code, but more fragile and scattered around.

Sometimes you can mix and match these two, such as having a broad notion of “I am in the main game now” or “I am in the main menu now”, but having popups that can be reused anywhere, like “You unlocked an achievement!”, etc.

1 Like

Wow, I’m amazed with all these responses, thank you Kurt, Spiney and Olipool!!
I’ll try get through everything!

I totally agree, in my opinion this is something I should’ve taken seriously and spent time making it clean and easily accessible. My UI is soo tangled up I’m opening and closing basically everything through 10+ scripts. I should’ve made a better approach to this which I really regret now, but oh well :frowning:

Something I did think about, but what can I find common, I did have an idea for closing buttons. I was gonna

  1. Create a list of the panels
  2. Basically just making a call saying this.gameobject.SetActive(false)
  3. Attach it to every panel, and In the OnClick function in the button component, call this, didn’t try it but should’ve.
    What type of ‘common factors’ are you referring to?

I agree, I guess im gonna try this. What I will do is I’ll just export all the ui I have and create a new game, and do all the fiddling around in there, or I might just create a duplicate copy of the game and try this out their!

I’m gonna leave this post till the end, I am not very familiar with datastacks, so I’m gonna spend time going through your links and I’ll get back to you.

I also did have another idea, but it’s incomplete, I tried by first making a script, creating a string called panel name, then adding that script to every button which opens a panel. Then I created a list of all the panels. what I wanted to do is that if the string/panel name is contained in the list of panels, it opens that panel, but I’m unsure how to actually compare and check if it exists with that name, I’m just using this for the list public GameObject[ ] Panels; Anyways…

How does this show the window you want it to, because as far as I understand, your putting all the panels in a list, then it grabs all those ones in the current scene and put it in a dictionary, I’m actually a bit confused on what your doing.

Something which is easy to do ig, but again, its much more messy and is not accessible at all, you’ll have to keep adding to that every time you add something.

1 Like

I will give you a few code examples:
At the start, the UIManager searches for all panels that are windows, meaning they have scripts attached to them that derive from the base class “Window”.

_windowsDict = new Dictionary<Type, Window>();
            foreach (Window window in FindObjectsOfType<Window>(true))
            {
                _windowsDict.Add(window.GetType(), window);
            }

Then what GetWindow does is:

public T GetWindow<T>() where T: Window
        {
            Window window;
            if (_windowsDict.TryGetValue(typeof(T), out window))
            {
                window.ResetValues(); //go back to defaults that may be overridden by a special case window (yes/no buttons, invokeEvents)
                return window as T;
            }

            Debug.LogError("Window of type " + nameof(T) + " not found", this);
            return null;
        }

And Show is a method on the base Window class that every window then can use, so every window opens in the same way. That can just be a setActive() or a fade in or it can trigger events etc.
My Show method so far:

public void Show()
        {
            Show(null);
        }
        public void Show(Window origin)
        {
            if (gameObject.activeSelf) Debug.LogWarning("Opening window "+this.GetType().Name+" while it is still open!", this);
          
            _origin = origin;
            gameObject.SetActive(true);
            onBeforeShow.Invoke(this);
            StartCoroutine(FadeInSequence());
            UIManager.Instance.SetCloseWindowsPanel(this);
        }

So you can tell a window from what window it was opened in case you want to go back to that window later, you can set an event that will be triggered before the window is shown (for example set values in the window), here in my case the window gets faded in by a coroutine and finally, I set a full-screen panel directly behind the window so when the user clicks next to a window it either closes or the mouse click gets caught and does not trigger other UI elements.

I will attach the UIManager, Window base class and an example window to this post. Let me know if you have any further questions :slight_smile: Please not that it is work in progress :wink:

8416002–1112781–SellOilWindow.cs (4.13 KB)
8416002–1112784–UIManager.cs (8.43 KB)
8416002–1112787–Window.cs (4.83 KB)

1 Like

Im gonna be honest, this is wayy out of my league, so I’m gonna step back on this for the time being, on your Window.cs, I can see what your doing, but I don’t understand how your choosing which panel is being opened, but yeah.

Good luck with your game and I hope you find a solution for this!!

I’d just like to share what I’m doing, I’m using Kurts suggestion of loading scenes additively and it’s working great!

    public string PanelName;

    public void OpenPanel()
    {
        SceneManager.LoadScene(PanelName, LoadSceneMode.Additive);
    }
1 Like