You will have to use many panels if you are a game developer. Most of the time (for beginners), the panels are open and close from their respective scripts or a single script with all their references.

Opening and closing the panel from the designated button click are easy. However, there are times when you will be frustrated when you have to search for each script related to the panel you want to close – that is when you want to close the open panel by pressing the Esc button.

Classic Method Using Booleans

Early in my game development career, I often used a boolean for each panel's open status. I used that boolean to check and close the panel if it is open. But by doing that, you will have to add a new boolean every time you add a new panel in the game.

Here's what the code would look like.

if (_isPanel1Open)
{
    //close the panel1
}

if (_isPanel2Open)
{
    //close the panel2
}

This way of handling the panel is very hard and not practical for large game projects where you must use numerous panels. So to handle the project with minimum and effective code, I have created a system I will describe now.

Better Method Using Scripts

The system I developed has two scripts, Mother and Child. These two scripts are responsible for tracking the open panels, their count and closing actions. By only using these two scripts, you will be free of having to call each script, holding the panel to close them.

I am explicitly saying "closing of the panel". It is because the opening of the panel is done from its relative scripts. The panel will be handled by this panel handling system only after it has been opened.

Mother Script

It is the main script, and there can only be one of the scripts in a single scene.

  • It is responsible for keeping track of the open panels' count and closing the opened panel when the Esc button is pressed.
  • It holds the list of child scripts or their IDs, which will be used to communicate with the child script.
  • Generally, the list is used by the main script to command the child script to close its panel.

Child Script

The child script is attached to a single panel and mainly handles the closing action of the panel.

  • The child script informs the mother script when its panel is opened and closed. In response, the total count of the open panels in the mother script is increased when the panels are opened and decreased when they are closed.
  • If the panel uses animations for opening and closing, it is also handled in this script.

Details with Code Example

Let's start with the actual coding part of the panel handling.

First, let's create a mother script PanelHandler.cs and a child script PanelScript.cs.

✍️
The communication between these two scripts is done through events. The events are handled by another script separately for ease. You can use the event in the main script if you want.

Let's create a PanelsHandlerEvent.cs script to handle the events for the panels, and its code looks like this.

public class PanelsHandlerEvent : MonoBehaviour
{
    #region Events for handling the panels

    public static IntEvent InformPanelClose = new IntEvent();
    public static IntEvent RequestPanelClose = new IntEvent();

    #endregion


    public class IntEvent : UnityEvent<int>
    {
    }
}
PanelsHandlerEvent.cs
  • InformPanelClose event is used by the child script to inform the mother script that it is closing the panel.
  • RequestPanelClose event is used by the mother script to command or request the child script to close the panel.

Now, introduce the required references and variables of the mother and child script, which are as follows.

PanelHandler.cs

private static PanelsHandler _instance { get; set; }

public static PanelsHandler GetInstance()
{
    return _instance == null ? FindObjectOfType<PanelsHandler>() : _instance;
}

private static int currentPanelIndex;

/// <summary>
/// List of the ids of the child panels that are currently open in the scene
/// </summary>
private List<int> openPanelIndexList = new List<int>();


/// <summary>
/// event to command the child script to close the panel
/// </summary>
public UnityEvent<int> RequestPanelClose = new UnityEvent<int>();
PanelHandler.cs

Here, currentPanelIndex represents the currently open index, i.e. ID of the currently open panel. openPanelIndexList stores the ID of the panels that have been opened in the scene.

The ID is removed from openPanelIndexList when the respective panel is closed. There is a Unity event called RequestPanelClosethat will be used to command the child script to close their panel.

PanelScript.cs

/// <summary>
/// Id of the panel
/// </summary>
public int index;

/// <summary>
/// Status to determine whether to hide/disable the panel or destroy when closing the panel
/// </summary>
[SerializeField] private bool _needToBeDestroyed = false;

/// <summary>
/// Status determining if the disable function is called or not
/// </summary>
private bool _disabledCalled = false;

/// <summary>
/// Reference of the main script of the panel handler
/// </summary>
private PanelsHandler _panelsHandler;
PanelScript.cs

In PanelScript.cs we add a listener to the event, which will provide a close request from the PanelHandler.cs script and ask to update the panel ID when enabled.

private void Awake()
{
    PanelsHandlerEvent.RequestPanelClose.AddListener(CloseThisPanel);
}


private void OnEnable()
{
    _disabledCalled = false;
    index = PanelsHandler.GetMyIndex();
    // play animation to open the panel
}
Awake() Function

When the ID of the currently opened panel is provided to PanelHandler.cs, it is added to the list.

public static int GetMyIndex()
{
    currentPanelIndex++;
    _instance.openPanelIndexList.Add(currentPanelIndex);

    return currentPanelIndex;
}
Current Panel Function

When closing the panel, the PanelScript.cs informs the PanelHandler.cs script and hides or destroys the panel.

✍️
To close the panel, call the CloseIt() method from the close button-click.
/// <summary>
/// Close the panel
/// </summary>
public void CloseIt()
{
    _disabledCalled = true;
    PanelsHandlerEvent.InformPanelClose?.Invoke(index);
    HideOrDestroy();
}


/// <summary>
/// Hide or destroy the panel
/// </summary>
private void HideOrDestroy()
{
    try
    {
        if (_needToBeDestroyed)
        {
            Destroy(gameObject);
        }
        else
        {
            gameObject.SetActive(false);
        }
    }
    catch (Exception e)
    {
        PrintLog.Log("NullReference exception: " + e);
    }
}
Close Panel Code

This method will run if the panel is not disabled directly from the editor.

private void OnDisable()
{
    if (_disabledCalled) return;
    CloseIt();
}

When the PanelScript.cs runs the InformPanelClose event, the following method is called in the PanelHandler.cs script to remove the data of the closing panel.

private void RemoveThePanelInfo(int index)
{
    if (openPanelIndexList == null || openPanelIndexList.Count == 0) return;
    openPanelIndexList.Remove(index);
    currentPanelIndex--;
}

Till now, the closing of the panel is done only after it has been called from the child script i.e PanelScript.cs. Let's close the open panel when the Esc button is clicked.

For this, add the following code in Update() function of PanelHandler.cs file.

if (!Input.GetKeyDown(KeyCode.Escape)) return;

if (openPanelIndexList.Count > 0)
{
    CloseCurrentPanel();
}
else
{
    OpenExitPanel();
}

Here, we have to handle what to do when the panel count is 0 or greater. In the code above, the panels are closed if there are any open panels. If there aren't any opened panels when the Esc button is pressed, it opens the exit panel.

When closing the current panel from RequestPanelClose, another event is invoked to request/command the child script of the provided ID to close its panel.

public void CloseCurrentPanel()
{
    PanelsHandlerEvent.RequestPanelClose?.Invoke(currentPanelIndex);
}

Add a listener event to handle the close request sent from the PanelHandler.cs script in PanelScript.

private void Awake()
{
    PanelsHandlerEvent.RequestPanelClose.AddListener(CloseThisPanel);
}

The PanelScript.cs file checks the ID provided and closes the panel if it matches its ID.

/// <summary>
/// close the panel after being commanded from the main script
/// </summary>
/// <param name="index"></param>
private void CloseThisPanel(int index)
{
    if (this.index != index) return;

    HideOrDestroy();
    //instead of directly calling the hide or destroy you can play close animation here
}

This is all the normal explanation of handling the open panels in the scene. There may have been some omission of the codes, so its full code is given below.

Full Code for PanelHandler.cs

using System.Collections.Generic;
using UnityEngine;

public class PanelsHandler : MonoBehaviour
{
    private static PanelsHandler _instance { get; set; }

    /// <summary>
    /// id of the currently opened panel.
    /// </summary>
    private static int currentPanelIndex;

    /// <summary>
    /// list of the id  of the open panels
    /// </summary>
    private List<int> openPanelIndexList = new List<int>();

    private void Awake()
    {
        PanelsHandlerEvent.InformPanelClose.AddListener(RemoveThePanelInfo);
    }

    private void Start()
    {
        _instance = this;
    }


    private void Update()
    {
        if (!Input.GetKeyDown(KeyCode.Escape)) return;

        BackButtonPressed();
    }


    private void BackButtonPressed()
    {
        if (openPanelIndexList.Count > 0)
        {
            CloseCurrentPanel();
        }
        else
        {
            OpenExitPanel();
        }
    }

    private void OpenExitPanel()
    {
        //open the exit panel 
    }


    public void CloseCurrentPanel()
    {
        PanelsHandlerEvent.RequestPanelClose?.Invoke(currentPanelIndex);
    }

    /// <summary>
    /// This method is used to remove all the existing panel of the scene from the stack
    /// </summary>
    public void CloseAllPanels()
    {
        if (openPanelIndexList == null || openPanelIndexList.Count == 0) return;
        foreach (var index in openPanelIndexList)
        {
            PanelsHandlerEvent.RequestPanelClose?.Invoke(index);
        }

        openPanelIndexList.Clear();
    }

    public static int GetMyIndex()
    {
        currentPanelIndex++;
        _instance.openPanelIndexList.Add(currentPanelIndex);

        return currentPanelIndex;
    }

    private void RemoveThePanelInfo(int index)
    {
        if (openPanelIndexList == null || openPanelIndexList.Count == 0) return;
        openPanelIndexList.Remove(index);
        currentPanelIndex--;
    }
}
Complete code for PanelHandler.cs

Full Code for PanelScript.cs

using System;
using UnityEngine;

[Serializable]
public class PanelWithDotWeen : MonoBehaviour
{
    /// <summary>
    /// Id of the panel
    /// </summary>
    public int index;

    /// <summary>
    /// Status to determine whether to hide or destroy the panel on close
    /// </summary>
    [SerializeField] private bool _needToBeDestroyed = false;

    /// <summary>
    /// Status to determine if the disable function has been called or not.
    /// </summary>
    private bool _disabledCalled = false;

    private void Awake()
    {
        PanelsHandlerEvent.RequestPanelClose.AddListener(CloseThisPanel);
    }


    private void OnEnable()
    {
        _disabledCalled = false;
        index = PanelsHandler.GetMyIndex();
        // play animation to open the panel
    }

    private void OnDisable()
    {
        if (_disabledCalled) return;
        CloseIt();
    }

    /// <summary>
    /// close the panel after being commanded from the main script
    /// </summary>
    /// <param name="index"></param>
    private void CloseThisPanel(int index)
    {
        if (this.index != index) return;

        HideOrDestroy();
        //instead of directly calling the hide or destroy you can play close animation here
    }

    /// <summary>
    /// Close the panel
    /// </summary>
    public void CloseIt()
    {
        _disabledCalled = true;
        PanelsHandlerEvent.InformPanelClose?.Invoke(index);
        HideOrDestroy();
    }


    /// <summary>
    /// Hide or destroy the panel
    /// </summary>
    private void HideOrDestroy()
    {
        try
        {
            if (_needToBeDestroyed)
            {
                Destroy(gameObject);
            }
            else
            {
                gameObject.SetActive(false);
            }
        }
        catch (Exception e)
        {
            Debug.Log("NullReference exception: " + e);
        }
    }
}
Complete Code for PanelScript.cs
Thank you so much for reading till the end. If you have any queries regarding the article, please leave a comment below. Until next time! Keep Coding!