Sponsored By

UI Programming in Unity3D

How to create a simple and manageble UI system in Unity3D

Anton Semchenko, Blogger

July 2, 2017

3 Min Read

The first commercial project which I was involved in was a first-person puzzle game, Fabric. For the most part, I programmed a level editor. As you can imagine, UI was a very big part of it. The way I did it was well... let's just say it was not a very good implementation. So for my next projects, I decided to develop a better system and here is what I come up with.

The first thing I thought was that there should be one class to rule them all. So I named it UIManager. This is the only UI - related class which other components will know about. It will bring requested UI elements to screen and tell them what to do. And since there will be only one UIManager at a time, I made it a singleton. To easily switch between different UI states (main menu, in game, pause etc) I decided to create a panel for each. Now I have MainMenuPanel, InGamePanel, and PausePanel in my hierarchy. As I mentioned above, the UIManager will only tell these panels what to do. Knowing how to do it should be their responsibility. For this reason, creating a script for each of these panels seems like a good idea. Also, I created an enum to use during the switching process.

Now I have a UIManager class which switches between panels and sometimes tells them what to do. Here is what it looks like:


using UnityEngine;

public enum Panel
{
    Main,
    InGame,
    Pause,
    None
}

public class UIManager : Singleton<UIManager>
{

    [SerializeField]
    private MainMenuPanel mainMenuPanel;
    [SerializeField]
    private InGamePanel inGamePanel;
    [SerializeField]
    private PausePanel pausePanel;

    public void SetPanel(Panel type)
    {
        mainMenuPanel.gameObject.SetActive(type == Panel.Main);
        inGamePanel.gameObject.SetActive(type == Panel.InGame);
        pausePanel.gameObject.SetActive(type == Panel.Pause);
    }
}

Now I can easily isolate the functionality of each panel. In case I need to call a specific method from a certain panel it will be done through UIManager as well. For example, let's create a "FadePanel", which will be responsible for fading in and out on a request. Here is a code for that specific panel:


using System;
using System.Collections;
using UnityEngine;

[RequireComponent(typeof(CanvasGroup))]
public class FadePanel : MonoBehaviour
{
    private CanvasGroup cGroup;
    public float fadeSpeed = 5;
    public float middleWaitTime = 0.5f;
    [HideInInspector]
    public bool inProgress = false;

    void Awake()
    {
        cGroup = GetComponent<CanvasGroup>();
    }

    public IEnumerator Fade(Action finalCallback, params Action[] callback)
    {
        inProgress = true;
        while (cGroup.alpha < 0.95f) 
        { 
            cGroup.alpha = Mathf.Lerp(cGroup.alpha, 1, Time.deltaTime * fadeSpeed);
            yield return null;
        } 
        cGroup.alpha = 1;
        if (callback != null)
            foreach (Action a in callback)
                a.Invoke();
        yield return new WaitForSeconds(middleWaitTime);
        
        while (cGroup.alpha > 0.05f)
        {
            cGroup.alpha = Mathf.Lerp(cGroup.alpha, 0, Time.deltaTime * fadeSpeed);
            yield return null;
        }

        if (finalCallback != null)
            finalCallback.Invoke();

        cGroup.alpha = 0;
        inProgress = false;
    }
}

Now we can call Fade() method from UIManager by adding a couple lines of code:


using UnityEngine;

public enum Panel
{
    Main,
    InGame,
    Pause,
    Fade,
    None
}

public class UIManager : Singleton<UIManager>
{

    [SerializeField]
    private MainMenuPanel mainMenuPanel;
    [SerializeField]
    private InGamePanel inGamePanel;
    [SerializeField]
    private PausePanel pausePanel;
    [SerializeField]
    private FadePanel fadePanel;
    
    public void Fade(Action finalCallback, params Action[] callback)
    {
        StartCoroutine(fadePanel.Fade(finalCallback, callback));
    }

    public void SetPanel(Panel type)
    {
        mainMenuPanel.gameObject.SetActive(type == Panel.Main);
        inGamePanel.gameObject.SetActive(type == Panel.InGame);
        pausePanel.gameObject.SetActive(type == Panel.Pause);
    }
}

In order to call the fade out - fade in effect all I need is to call Fade() method on the UIManager.

I've been using this design in my last few projects and so far I have found it very useful, organized and easy to debug. Please let me know if this is a bad practice in some way or if it can be improved. I would like to learn a better approach for organizing the UI if you know any.

Read more about:

Blogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like