Multiplayer 5. The modal manager

The essence of a good UI is boxes. Lots and lots and lots and lots of boxes!

The essence of a good UI is control. Leading your users down paths you want them to take in the simplest and shortest manner while minimising the chances of them getting lost. A simple way to do this is to keep them informed on every step and decision they make so that they know they are progressing the way they expect.

To help them, we're going to build a simple Modal manager. A modal window is a popup you'll display to the user to let them know we're either waiting for something from them, like getting them to agree to soul-owning terms and conditions. We're also going to use it to let them know the game's busy doing something, like trying to connect, and they should hold on a moment.

To keep things simple, we're also going to want to support three types of popup. One is going to be a simple loading box with no buttons. As mentioned before, the job of this is to inform the user that the game is doing "something".

The next type is going to have a single button so we can either let the user know we're doing something and they can cancel it, or we're waiting for their agreement before we proceed. The last type will have two buttons, an ok and a cancel button. We'll use these when we're asking the user a question they need to respond to.

So what do we need to accomplish this? Well, this manager's going to be created in 3 steps. We're going to define the code that controls the modal window, define a manager for that code, and lastly create the UI for it. 

Modal control

Let's get started then. The code below handles displaying one of three types of modals, depending on which text labels have been set. Whenever we want to use a new modal we'll create a new instance of it, and when we're done we'll destroy it. As such, we only need to worry about creating and showing a modal as it's never going to be hidden or altered once live.

// Modal/ModalUI.cs

using UnityEngine;
using UnityEngine.UI;

namespace Modal {
    public class ModalUI : MonoBehaviour {

        public GameObject simpleModal;
        public GameObject okModal;
        public GameObject cancelModal;

        public Text simpleTitle;
        public Text okModalTitle;
        public Text okButtonTitle;
        public Text cancelModalTitle;
        public Text cancelOKButtonTitle;
        public Text cancelCancelButtonTitle;

        public delegate void ButtonResponse ();

        private ButtonResponse okButtonResponse;
        private ButtonResponse cancelButtonResponse;

        public ModalUI SetTitleText (string titleText)
        {
            simpleTitle.text = titleText;
            okModalTitle.text = titleText;
            cancelModalTitle.text = titleText;

            return this;
        }

        public ModalUI SetOKButtonText (string okButtonText)
        {
            okButtonTitle.text = okButtonText;
            cancelOKButtonTitle.text = okButtonText;

            return this;
        }

        public ModalUI SetOKButtonResponse (ButtonResponse okResponse)
        {
            okButtonResponse = okResponse;

            return this;
        }

        public ModalUI SetCancelButtonText (string cancelButtonText)
        {
            cancelCancelButtonTitle.text = cancelButtonText;

            return this;
        }

        public ModalUI SetCancelButtonResponse (ButtonResponse cancelResponse)
        {
            cancelButtonResponse = cancelResponse;

            return this;
        }

        public void Show ()
        {
            if (cancelCancelButtonTitle.text != "") {
                cancelModal.SetActive(true);
            }
            else if (okButtonTitle.text != "") {
                okModal.SetActive(true);
            }
            else {
                simpleModal.SetActive(true);
            }
            gameObject.SetActive(true);
        }

        public void OKButtonClick ()
        {
            okButtonResponse();
        }

        public void CancelButtonClick ()
        {
            cancelButtonResponse();
        }
    }
}

All of the Set*() at the top of the class are used to define what our modal will look like and do, setup with a standard method chaining pattern. After they've been set, Show() can be called so the user can see it. The last two methods are called by the modal UI to trigger the callbacks in a neat way.

The modal's boss

Now we have our modal, we need to create it's line manager to tell it what to do. The following code shows a semi-singleton pattern, spliced into a standard MonoBehaviour. It's useful to let Unity do it's magic as a service locator as we can setup a prefab with our modal UI already wired in correctly, but we also want to call this class without any hassle from a number of different locations around our code. We could always pass a reference wherever we need it, but for the sake of speed and a tutorial we're allowing direct access to our instance.

// Modal/ModalManager.cs

using UnityEngine;

namespace Modal {
    public class ModalManager : MonoBehaviour {

        private static ModalManager manager;
        private GameObject existingInstance;
        public GameObject prefab;

        public ModalManager ()
        {
            ModalManager.manager = this;
        }

        public static ModalManager GetInstance ()
        {
            return manager;
        }

        public void Show (string title, string okButtonTitle, string cancelButtonTitle, ModalUI.ButtonResponse okButton, ModalUI.ButtonResponse cancelButton)
        {
            Hide();
            existingInstance = Instantiate(prefab);

            existingInstance.GetComponent<ModalUI>()
                .SetTitleText(title)
                .SetOKButtonText(okButtonTitle)
                .SetOKButtonResponse(okButton)
                .SetCancelButtonText(cancelButtonTitle)
                .SetCancelButtonResponse(cancelButton)
                .Show();
        }

        public void Show (string title, string okButtonTitle, ModalUI.ButtonResponse okButton)
        {
            Show(title, okButtonTitle, "", okButton, null);
        }

        public void Show (string title)
        {
            Show(title, "", "", null, null);
        }

        public void Hide ()
        {
            if (existingInstance) {
                Destroy(existingInstance);
            }
        }
    }
}

It's the manager here that supports the three types of modal window we'll display, and also a method for disposing of our modal once it's complete.

Window dressing

Last up for this, we need to show how it looks and how everything links together. For all of these tutorials we're going to use the same, simple style for everything. This is so you can easily just reskin it in future, or use it as a basis for something a lot more awesome.

First we'll create a simple transparent background for our modals. This is mainly to stop users from pressing any buttons we don't want them to. Note that after you've created all of the modal types below you'll need to come back here and attach all of the titles and buttons to the ModalUI script.
The first type, the simple modal, is made of a basic image panel and text game object.
When it is enabled, it'll look something like the above, hopefully.
You may need to click on this image to read all of the details correctly. These are all of the settings you'll need for the single-button modal window...
... which when complete should look like the above.
The last modal type is the two-button modal window. There are a lot of settings here so I've split them into two. The first settings are here. Click on the image to get a larger view.
The second part of this last type's settings are here. There's nothing new if you've followed the previous settings. You can also click on this image to get a larger view.
When implemented, you should have a modal similar to the one above.

After you've implemented all of the above, check you've linked all titles and buttons back to the main game object, and disabled the modals by default. Then create a prefab of this modal and call it ModalPrefab (original title, I know).

You may notice the fonts used are non-standard and relatively clean (for Unity's standard of fonts). This was achieved by importing a single font twice, at two different sizes. The first is size 20, and the second at size 24.

To do this, import a font simply by adding it to your somewhere in your assets folder. For the UI above I've used the always pretty OpenSans, available for free with a good license terms. Then I've fixed a size and set the import to include all of Unicode (as we don't know yet what we're going to need). You may need to set the rendering mode to either "Hinted Smooth" or "Smooth" depending on the font.

Pretty fonts in Unity that aren't blurry in one (two) easy step(s).

Always end on an example

Now we have our modal prefab, it's time to put it into use. We're going to make use of the NetworkManager always existing between scenes in our game by attaching the ModalManager script to it, and then linking the prefab to it.

Doing this will mean the modal manager is instantiated when the game loads, is available in all scenes, and (as it's a singleton) is available to any of the code. 

The modal manager in all of it's glory.

Now it's attached, if we revisit the ChangeSceneButton code from the last blog post, we can add in a check so that whenever someone tries to change scene, a popup will appear for confirmation.

// ChangeSceneButton.cs

using Modal;
using UnityEngine.Networking;

public class ChangeSceneButton : NetworkBehaviour {

    private string storedSceneName;

    [Server]
    public void ChangeScene (string sceneName)
    {
        ModalManager.GetInstance().Show(
            "Ready to load scene '" + sceneName + "'", 
            "Do it", 
            "Not yet", 
            () => {
                NetworkManager.singleton.ServerChangeScene(sceneName);
            }, 
            () => {
                ModalManager.GetInstance().Hide();
            }
        );
    }
}

Note the use of anonymous functions for the callbacks. I prefer how it looks instead of having additional methods for simple requests.

Possible updates

That's it for the moment. This is the version we're going to use for the rest of these tutorials, but there are some updates you could choose to make. One would be keyboard / controller button shortcuts for the popup buttons.

Another change that could be useful for your game is the possibility of having multiple modal windows layered over each other. This code won't currently allow it but with minor modifications it could do. You'd just need to keep track of the latest layer using a stack.

Next blog we eventually get back to the UI, starting with the main menu.

You can get the source code for the blog post from github.com/dittto/unity-multiplayer-5.