Posts
Search
Contact
Cookies
About
RSS

Simple Yes No dialog for Unity

Added 10 Feb 2021, 4:45 p.m. edited 18 Jun 2023, 1:12 a.m.

Confirming an action, for example when the user clicks exit on an exit button in a main menu, is one of those nice touches that just add that little bit more polish and user convenience. As such its something that should be in the toolkit of every Unity coder.

dialog prefab

Creating the prefab for the dialog is fairly trivial, its just a UI Canvas containing a Panel with a double line of text and two buttons, you'll probably find the code a little more enlightening...

Lets look first at a very simple main menu with two buttons ...

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.SceneManagement;

public class MenuController : MonoBehaviour
{
    public Canvas canvas;
    public GameObject ynPrefab;
    
    private Coroutine quitCoroutine = null;

    public void NewGameClicked() 
    {   
        PlayerControl player = PlayerControl.Instance;
        SceneManager.LoadScene("Scenes/exteriorAlpha");
    }

    public void QuitClicked()
    {
        if (quitCoroutine == null) {
            quitCoroutine = StartCoroutine(ShowDialog("Are you sure you want to quit ?"));
        }       
    }

    IEnumerator ShowDialog(string title)
    {
        GameObject dialogObj = Instantiate(ynPrefab, canvas.transform); // instantiate the UI dialog box
        dialogObj.transform.SetParent(canvas.transform);
        
        YnDialog dialog = dialogObj.GetComponent<YnDialog>();
        dialog.setTitle(title);
        
        while (dialog.status == YnDialog.STATUS.WAITING) {
            //Debug.Log("Yielding");
            yield return null; // wait
        }

        if (dialog.status == YnDialog.STATUS.YES) {
            Debug.Log("decided to quit");
            Application.Quit();          
            // "click" stop if in editor, iky #defines!
            #if UNITY_EDITOR
                EditorApplication.isPlaying = false;
            #endif
        } else {
            Debug.Log("decided not to quit");
        }

        Destroy(dialog.gameObject);
        quitCoroutine = null;
    }
}

The Main Menu has its own canvas with just two buttons, New Game and Quit where the dialog is used.

There is a little gottcha here if you've never used UI buttons before. When you populate a buttons OnClick list, unfortunately it will let you drop any object into the list, however you need a concrete instance (usually a GameObject) once you have dropped the MainMenu instance (from the scene) into one of the dialogs OnClicked List items you should be able to see all the Public methods in the MainMenu

    public void QuitClicked()
    {
        if (quitCoroutine == null) {
            quitCoroutine = StartCoroutine(ShowDialog("Are you sure you want to quit ?"));
        }       
    }

You can think of a Coroutine as a kind of lightweight thread, what makes them really handy in this case is that they can yield their execution until the next frame, basically just sat waiting for us slow human to do something...

Notice also that I'm passing a string to the Coroutine, this is used to replace the title text and should form some kind of Yes/No question. This makes the YnDialog more reusable, whether you're asking to quit the game or confirm their turn should end in a strategy type game.

    IEnumerator ShowDialog(string title)
    {
        GameObject dialogObj = Instantiate(ynPrefab, canvas.transform); // instantiate the UI dialog box
        dialogObj.transform.SetParent(canvas.transform);
        
        YnDialog dialog = dialogObj.GetComponent<YnDialog>();
        dialog.setTitle(title);
        
        while (dialog.status == YnDialog.STATUS.WAITING) {
            //Debug.Log("Yielding");
            yield return null; // wait
        }

        if (dialog.status == YnDialog.STATUS.YES) {
            Debug.Log("decided to quit");
            Application.Quit();         
            // "click" stop if in editor, iky #defines!
            #if UNITY_EDITOR
                EditorApplication.isPlaying = false;
            #endif
        } else {
            Debug.Log("decided not to quit");
        }

        Destroy(dialog.gameObject);
        quitCoroutine = null;
    }

The first thing to do is Instance (or create) the dialog from the prefab, and the dialog parent is attached to the MainMenu Canvas. With a reference to the YnDialog's script instance the can set the title in this case asking the user if they really meant to click the Quit Button.

        while (dialog.status == YnDialog.STATUS.WAITING) {
            //Debug.Log("Yielding");
            yield return null; // wait
        }

While the dialog is running its state can be queried with the status property, if the user hasn't yet clicked a response the while loop yields till the next frame.

            // "click" stop if in editor, iky #defines!
            #if UNITY_EDITOR
                EditorApplication.isPlaying = false;
            #endif

As a little bonus I added some code to stop the Unity player if its running in the editor, as you can't access the editor in a runtime build, this code needs protecting from the compiler when you're building a binary. I'm a little lukewarm about using preprocessor defines but in this case the editor simply doesn't exist at runtime in an independent build. Actually stopping the player in the editor though does make a difference and allows you to see that something actually is happening !

With the dialog monitoring each frame for a response, the missing, the missing part of the puzzle is the actual YnDialog script.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class YnDialog : MonoBehaviour
{
    public enum STATUS { WAITING, NO, YES }
   
  private STATUS _status = STATUS.WAITING;
  
  public STATUS status 
 {
     get { return _status; }
       private set { _status = value; }    
  }

   public void setTitle(string title)
    {
     GameObject textO = GameObject.Find("title");
        Text text = textO.GetComponent<Text>();
        text.text = title;
    }
 
  // GUI callbacks
  public void YesClicked() 
 {
     status = STATUS.YES;
  }   
  
  public void NoClicked()
   {
     status = STATUS.NO;
   }
}

While normally it would be good practice to cache component references in the Start Method, as the title is only ever set once there seems little point.

So armed with the above clues you should be in a good position to implement your own dialogs based on this simple example

Enjoy!