Programming

UI Programming in Unity3D

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. To easily switch between different UI states (main menu, in game, pause etc) I decided to create a canvas for each. Now I have MainMenuCanvas, InGameCanvas, and PauseCanvas 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 : MonoBehaviour
{

    [SerializeField]
    private static MainMenuCanvas mainMenuCanvas;
    [SerializeField]
    private static InGameCanvas inGameCanvas;
    [SerializeField]
    private static PauseCanvas pauseCanvas;

    public static void SetPanel(Panel type)
    {
        mainMenuCanvas.gameObject.SetActive(type == Panel.Main);
        inGameCanvas.gameObject.SetActive(type == Panel.InGame);
        pauseCanvas.gameObject.SetActive(type == Panel.Pause);
    }
}

Now I can easily isolate functionality of each canvas. In case I need to call a specific method from a certain canvas 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 canvas:

using System;
using System.Collections;
using UnityEngine;

[RequireComponent(typeof(CanvasGroup))]
public class FadeCanvas : 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 couple lines of code:

using UnityEngine;

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

public class UIManager : MonoBehaviour
{

    [SerializeField]
    private MainMenuCanvas mainMenuPanel;
    [SerializeField]
    private InGameCanvas inGamePanel;
    [SerializeField]
    private PauseCanvas pausePanel;
    [SerializeField]
    private FadeCanvas fadeCanvas;
    
    public void Fade(Action finalCallback, params Action[] callback)
    {
        StartCoroutine(fadeCanvas.Fade(finalCallback, callback));
    }

    public void SetPanel(Panel type)
    {
        mainMenuCanvas.gameObject.SetActive(type == Panel.Main);
        inGameCanvas.gameObject.SetActive(type == Panel.InGame);
        pauseCanvas.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 to organize the UI if you know any.