Better ways to Handle Open Panels in Game Scene

The system I developed has two scripts Mother and Child scripts. These two scripts are responsible for handling the tracking of the open panels, their count and their closing actions.

If you are a game developer then you will have to use many panels and most of the time (for beginners) the panels are open and close from their respective scripts or a single script with all their references. The opening and closing of the panel from the designated button click is easy but there are times when you will be frustrated when you will have to search for each script related to the panel you want to close and that is when you want to close the open panel by pressing escape button.

In the earlier day of my game development career, I often use boolean for each panel's open status and by checking that boolean I used 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 and the code will be like this:

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 will have to use many numerous panels. So to handle the project with minimum and effective code I have created a system that I will describe now.

The system I developed has two scripts Mother and Child scripts. These two scripts are responsible for handling the tracking of the open panels, their count and their closing actions. By only using these two scripts you will be free of the hassle of having to call each script holding the panel to close them. I am explicitly saying closing of panel. It is because the opening of the panel is done from its relative scripts and only after the panel has been opened that the panel will be handled by this panel handling system.

Simple descriptions of the scripts involved.

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 escape button is pressed.
  • It holds the list of the child script or their id 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 handles (mainly) the closing action of the panel.

  • The child script is responsible for informing the mother script in case of opening and closing of its panel in response the total count of the open panel in the mother script is increased when the panel is opened and decreased when the panel is closed.
  • If the panel uses animation for the opening and closing of the panel then it is also handled in this script.

Details with code example

Now let's start the actual coding part of the panel handling. First, let's create a mother script PanelHandler.cs and child script PanelScript.cs. The communication between these two scripts are done through events and 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 PanelsHandlerEvent.cs script to handle the events for the panels and its' code is 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

Here currentPanelIndex represent the currently open index i.e. id of the currently open panel's id and 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 RequestPanelClose a unity event that 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

Now,  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
}

In PanelHandler.cs when providing the id to the currently opened panel its id is added to the list.

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

    return currentPanelIndex;
}
PanelHandler

And when closing the panel the PanelScript.cs informs the PanelHandler.cs script and hide or destroy the panel.

Note: Call CloseIt() method from close button click to close the panel
/// <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);
    }
}

If the panel is not disabled directly from the editor then this method will run.

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

When the PanelScript.cs run 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--;
}

Up to now the closing of the panel is done only after it has been called from the child script i.e PanelScript.cs. Now let's move on to closing the open panel when the Escape button is clicked.

For this add the following code in Update() 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 count of the panel is 0 or greater. As you can see in the code above we are closing the panels if there is any opened panel and opening the exit panel if there aren't any opened panels when Escape button is pressed.

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

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

In PanelScript add to the listener event to handle the close request sent from the PanelHandler.cs script.

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

The PanelScript.cs file checks the id provided and close the panel if it matched 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 the handling of 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've any queries regarding the article please leave comments below. Until next time! Keep Coding!