Model-View-Controller In Unity’s UI

Share on facebook
Facebook
Share on google
Google+
Share on twitter
Twitter
Share on linkedin
LinkedIn

Introduction

After seeing dozens of projects each using a completely different system for their user-interface management I thought this was needed in this area. I really wanted to go into this design pattern because I think this would make UI programming in Unity intuitive and standardized. This just makes the most sense when tackling any UI anywhere in my opinion and its benefits are highlighted below. Note, this is going to use the concept of generics which I cover in my last blog post, Generics in C# using UnityEngine;.

What is MVC

MVC stands for Model View Controller. At an abstract level, the view would contain everything the user see’s and interacts with. The view would then send any user input to the controller. All the data the view might need would be contained in the Model. The controller is used to do any calls or run any actual code and respond to input from the view. So to recap so far, the View displays information from the Model and interacts with the Controller which implements the functionality.

The benefits of this system include a significantly faster development process once everything’s been set up, the ability to modularize your UI, and furthers support for asynchronous methods which don’t return something right away, for example sending a request to a server then waiting for a result.

Implementation

Let’s implement this now into code specifically using Unity’s libraries.

public class Controller<V, M> : MonoBehaviour where V: View where M: Model
{
    public V view;
    public M model;

    void OnEnable()
    {
        Enabled();
    }
    public virtual void Enabled() { }

    void OnDisable()
    {
        Disabled();
    }
    public virtual void Disabled() { }
}

public class Model
{

}

public class View
{

}

So what we’re doing here is creating our base classes. One for our Controller, Model, and View. Model and View are empty classes so it’s nothing to worry about. Our Controller inherits from MonoBehaviour accepts two generic types, one for anything that inherits from Model and one for View as well. Controller has two variables for each generic type. Next it has two methods for OnEnable and OnDisable and a virtual method for each that our controllers will override.

Implementation

Here’s an example of a solid explanation for an example login screen with functionality to save and load your user information.

Note: Each panel in your canvas should have its own Controller. 

public class LoginController : Controller<LoginView, LoginModel>
{

    public override void Disabled()
    {
        // Remove Button Pointers
        view.loginButton.onClick.RemoveAllListeners();
        view.backButton.onClick.RemoveAllListeners();

        // Save and do any final things before we turn off
        if (model.saveInfo)
        {
            SetSavedLogin(model);
        }
    }

    public override void Enabled()
    {
        // Update Button Listeners
        view.loginButton.onClick.AddListener(Login);
        view.backButton.onClick.AddListener(Back);

        // Set fields
        model = GetSavedLogin();
        view.usernameInput.text = model.username;
        view.passwordInput.text = model.password;
        view.saveInfoToggle.isOn = model.saveInfo;
    }

    LoginModel GetSavedLogin()
    {
        LoginModel model = new LoginModel();
        // Find our saved user/password using our data management system
        return model;
    }

    void SetSavedLogin(LoginModel model)
    {
        // Save our user/password using our data management system
    }

    void Login()
    {
        StartCoroutine(LoginCoroutine());
    }

    IEnumerator LoginCoroutine()
    {
        // Enable loading to tell the user something is happening and cover up any other input from being entered
        UIManager.instance.SetLoading(true);

        // Send request to backend or database with the username/password
Login(model.username, model.password); // Wait for response yield return new WaitForSeconds(1); // Disable loading UIManager.instance.SetLoading(false); if (success) UIManager.instance.OpenScreen("MainMenu"); } void Back() { UIManager.instance.OpenScreen("LoginRegisterMenu"); } } [System.Serializable] public class LoginModel : Model { public string username; public string password; public bool saveInfo; } [System.Serializable] public class LoginView : View { public InputField usernameInput; public InputField passwordInput; public Toggle saveInfoToggle; public Button loginButton; public Button backButton; }

Note: [System.Serializable] on both the LoginModel and LoginView

So lets take this bit by bit beginning with the LoginModel. The LoginModel has three fields, one for username, password, and a boolean for save information. This would be a check box asking you if you want to save your password or not.

Next the LoginView has fields for a username and password, a toggle for saving info, and two buttons for login and returning to the last screen. These will all be set in the Unity Editor after attaching your Controller to the panel.

Next the Controller. We are declaring a class that inherits from Controller and provides it with two types for its view and model, respectively. These are the LoginView and LoginModel. We then take advantage of the virtual methods created for Disabled and Enabled states.
Once enabled, it will add listeners for each button linking it to the Login method for the login button and Back method for the back button. Then we get the saved login model if it exists then update the input and toggle to represent the saved information.
Once disabled, it will remove any listeners from the buttons, then it will save the model if we selected to save.

Here’s how the hierarchy would look for something like this, with the LoginPanel containing the Controller component.

Expanding on this Example

This can’t be done without creating a solid UIManager, let’s do that. This will be a component on our Canvas that has a list of all the possible screens in the game and displays them as requested.
public class UIManager : MonoBehaviour
{

    public static UIManager instance;

    public string initialScreen = "Menu";

    [System.Serializable]
    public struct MenuScreen
    {
        public string name;
        public Controller<view, model=""> menuScreen;
    }
    public List menuScreens = new List();

    public MenuScreen activeScreen;

    void Awake()
    {
        instance = this;
        OpenScreen(initialScreen);
    }

    public void OpenScreen(string name)
    {
        for(int i = 0; i < menuScreens.Count; i++)
        {
            if (menuScreens[i].name.Equals(name))
            {
                activeScreen.menuScreen.gameObject.SetActive(false);
                activeScreen = menuScreens[i];
                activeScreen.menuScreen.gameObject.SetActive(true);
                break;
            }
        }
    }
}
It’s fairly self explanatory what this script does, we are simply containing a list of screens that contains the Controller as well as an identifier for them, a string.
 

Conclusion

To conclude, I believe this is the best design pattern for programming UI in Unity that I’ve encountered and allows for simple expansion and makes everything incredibly modular and abstract. In special UI cases you wont use a controller and write a script for it specifically but for the majority of cases in my UI I’ve used this design pattern without a hitch.

More to explore

Designing an Educational Game

A blog post covering my experience designing an educational game for 5th grade students in the US following common core standards.