What do you do if you want to use the Model-View-Controller (MVC) design pattern in your Unity app but you don’t want to use a framework like StrangeIoC? With a little thinking about the problem I think I’ve come up with a simple yet effective pattern to follow that doesn’t require you to use any framework. In today’s article I’ll talk about each part, how the parts fit together, and how you can use MVC to cleanly organize your “pure code” app. Whether you’re an MVC newbie or just want to see a new take on MVC in Unity, you’re sure to learn something today!

Model-View-Controller (MVC) comes in three parts which are, obviously, the Model, the View, and the Controller. Today I’ll be describing a particular, Unity-specific manifestation of this design pattern that strongly resembles many other MVC designs, but surely differs in some ways.

Let’s start with the Model. A Model is a class representing the data of a particular concept. The concept can be anything in your app that you need to represent: an enemy, the score HUD element, or a archery target. Let’s use an enemy as an example. Here’s what the enemy model would look like:

using System;
 
using UnityEngine;
 
// Dispatched when the enemy's position changes
public class EnemyPositionChangedEventArgs : EventArgs
{
}
 
// Interface for the model
public interface IEnemyModel
{
	// Dispatched when the position changes
	event EventHandler<EnemyPositionChangedEventArgs> OnPositionChanged;
 
	// Position of the enemy
	Vector3 Position { get; set; }
}
 
// Implementation of the enemy model interface
public class EnemyModel : IEnemyModel
{
	// Backing field for the enemy's position
	private Vector3 position;
 
	public event EventHandler<EnemyPositionChangedEventArgs> OnPositionChanged = (sender, e) => {};
 
	public Vector3 Position
	{
		get { return position; }
		set
		{
			// Only if the position changes
			if (position != value)
			{
				// Set new position
				position = value;
 
				// Dispatch the 'position changed' event
				var eventArgs = new EnemyPositionChangedEventArgs();
				OnPositionChanged(this, eventArgs);
			}
		}
	}
}

There are a few key things to understand about the above code. First, the model is just the data representation of the enemy. It doesn’t have any GameObject, MonoBehaviour, or anything else that Unity will actually display or keep as part of the scene. If it weren’t for using Vector3 from UnityEngine this code would be portable even outside of Unity.

The second key is that an event is dispatched whenever the model changes. This lets anyone who’s interested in tracking the model’s state know that it has changed. Part of this is that the set part of the model’s properties needs to check if the value is actually changing so as to avoid dispatching “changed” events when nothing has actually changed, potentially triggering expensive operations by those who are tracking the model.

Lastly, the model should always implement an interface so that other implementations of it can be used. This is particularly useful when unit-testing users of the model, but also allows for general flexibility at runtime.

Next up is the View. This part of the design pattern is the most Unity-specific. A View derives from MonoBehaviour to provide two functions: input and output. I use the word “input” broadly here as it covers anything from mouse, keyboard, and touch input to messages from Unity delivered via magic function names like OnCollisionEnter and Update. For more on these, check out Capturing and Forwarding Unity Events.

The “output” part of a view includes anything it does to effect the graphics and sound output of the app, including changing transforms, spawning particles, and playing sound effects. While Unity will let you do most of the “output” part without a MonoBehaviour, the “input” is only achieved by those magically-named functions.

Here’s an example view for the enemy:

using System;
 
using UnityEngine;
 
// Dispatched when the enemy is clicked
public class EnemyClickedEventArgs : EventArgs
{
}
 
// Interface for the enemy view
public interface IEnemyView
{
	// Dispatched when the enemy is clicked
	event EventHandler<EnemyClickedEventArgs> OnClicked;
 
	// Set the enemy's position
	Vector3 Position { set; }
}
 
// Implementation of the enemy view
public class EnemyView : MonoBehaviour, IEnemyView
{
	// Dispatched when the enemy is clicked
	public event EventHandler<EnemyClickedEventArgs> OnClicked = (sender, e) => {};
 
	// Set the enemy's position
	public Vector3 Position { set { transform.position = value; } }
 
	void Update()
	{
		// If the primary mouse button was pressed this frame
		if (Input.GetMouseButtonDown(0))
		{
			// If the mouse hit this enemy
			var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit;
			if (
				Physics.Raycast(ray, out hit)
				&& hit.transform == transform
			)
			{
				// Dispatch the 'on clicked' event
				var eventArgs = new EnemyClickedEventArgs();
				OnClicked(this, eventArgs);
			}
		}
	}
}

You may have noticed that there’s a certain symmetry between the model and the view. They’re similar, but different in very important ways. Both have an interface and a class implementing it, but it’s even more important that you make an interface when it comes to the view. When unit testing those who use the view you’ll definitely want to create a fake (a.k.a. “stub”, “mock”, “substitute”) view so that you don’t need an actual GameObject, don’t do any actual output (like playing sounds!), and don’t need any actual input so the test can be automated.

The view has events for any input that it would like to pass along to those using it to gather input. In this case the enemy dispatches an event when it is clicked, but it doesn’t take any direct action in response to the input. For example, the view does not highlight the enemy even though that would be an “output” function. It certainly doesn’t shoot the enemy or anything like that.

On the “output” side, the view provides set-only properties and other write-only functions so that it can take in data necessary to change the app’s graphics and sound output. In this case there is just a simple position setter that passes the Vector3 along to the GameObject transform.

Lastly we come to the Controller. This is the part of the pattern that binds the model and the view together. It has references to both of and acts to coordinate them. The controller listens to the model’s events and tells the view to update accordingly. It also listens to the view’s events and tells the model to update accordingly. Let’s take a look at a controller for the enemy:

using UnityEngine;
 
// Interface for the enemy controller
public interface IEnemyController
{
}
 
// Implementation of the enemy controller
public class EnemyController : IEnemyController
{
	// Keep references to the model and view
	private readonly IEnemyModel model;
	private readonly IEnemyView view;
 
	// Controller depends on interfaces for the model and view
	public EnemyController(IEnemyModel model, IEnemyView view)
	{
		this.model = model;
		this.view = view;
 
		// Listen to input from the view
		view.OnClicked += HandleClicked;
 
		// Listen to changes in the model
		model.OnPositionChanged += HandlePositionChanged;
 
		// Set the view's initial state by synching with the model
		SyncPosition();
	}
 
	// Called when the view is clicked
	private void HandleClicked(object sender, EnemyClickedEventArgs e)
	{
		// Do something to the model
		// This example just moves the model +1 along the X axis
		model.Position += new Vector3(1, 0, 0);
	}
 
	// Called when the model's position changes
	private void HandlePositionChanged(object sender, EnemyPositionChangedEventArgs e)
	{
		// Update the view with the new position
		SyncPosition();
	}
 
	// Sync the view's position with the model's position
	private void SyncPosition()
	{
		view.Position = model.Position;
	}
}

The controller should be very straightforward as it’s mostly just a middle-man between the model and the view. There are some details to take into account though. The first of these is that the controller should take interfaces for the model and view as parameters to its constructor so that alternate versions can be used. Unit testing a controller is where one of the biggest payoffs of having interfaces for the model and view comes into play. You can easily give it fake models and views to isolate your testing to just the controller. Additionally, providing an interface to the view means that the controller isn’t tempted to directly access all the properties of MonoBehaviour, like the transform or renderer.

Second is that you should also have an interface for the controller. It may be empty if your controller has no public functions—as in this example—but you may add functions later. This class, like the model, also doesn’t have any necessary dependencies on Unity so it’s quite portable in case you want to move one or both of them to a DLL used by a command-line app, a server, or any other .NET environment.

This takes care of the three main pieces of the pattern, but it’s often a good idea to create factory or builder classes to build one or all of the parts. Here are some example factories:

// Interface for the model factory
public interface IEnemyModelFactory
{
	// Get the created model
	IEnemyModel Model { get; }
}
 
// Implementation of the model factory
public class EnemyModelFactory : IEnemyModelFactory
{
	public IEnemyModel Model { get; private set; }
 
	// Create the model
	public EnemyModelFactory()
	{
		Model = new EnemyModel();
	}
}
 
// Interface for the view factory
public interface IEnemyViewFactory
{
	// Get the created view
	IEnemyView View { get; }
}
 
// Implementation of the view factory
public class EnemyViewFactory : IEnemyViewFactory
{
	public IEnemyView View { get; private set; }
 
	// Create the view
	public EnemyViewFactory()
	{
		var prefab = Resources.Load<GameObject>("Enemy");
		var instance = UnityEngine.Object.Instantiate(prefab);
		View = instance.GetComponent<IEnemyView>();
	}
}
 
// Interface of the view factory
public interface IEnemyControllerFactory
{
	// Get the created controller
	IEnemyController Controller { get; }
}
 
// Implementation of the controller factory
public class EnemyControllerFactory : IEnemyControllerFactory
{
	public IEnemyController Controller { get; private set; }
 
	// Create just the controller
	public EnemyControllerFactory(IEnemyModel model, IEnemyView view)
	{
		Controller = new EnemyController(model, view);
	}
 
	// Create the model, view, and controller
	public EnemyControllerFactory()
		: this(new EnemyModel(), new EnemyView())
	{
	}
}

Exactly how you implement your factory classes is up to you and, in the case of the view, very specific to your app’s assets. The model and controller factories are more straightforward, but may have some variation depending on how much or little you’d like them to do. You may have multiple factories for different kinds of construction or simply multiple constructors in a single factory. It’s your call, but the interfaces should remain so you can create more factories for different kinds of construction. For example, a WWWEnemyViewFactory could load an enemy model from the web with the WWW class.

For the final bit of code, let’s take a look at how you’d use these MVC classes in a Unity app. With all of the setup we’ve done so far, this part is really simple.

using UnityEngine;
 
public class MainScript : MonoBehaviour
{
	void Awake()
	{
		// Create the model
		var modelFactory = new EnemyModelFactory();
		var model = modelFactory.Model;
 
		// Set some initial state
		model.Position = new Vector3(1, 2, 3);		
 
		// Create the view
		var viewFactory = new EnemyViewFactory();
		var view = viewFactory.View;
 
		// Create the controller
		var controllerFactory = new EnemyControllerFactory(model, view);
		var controller = controllerFactory.Controller;
	}
}

It’s really just a simple matter of using the factories to build everything. In this case we don’t use the controller since it has no functions, but simply creating it allows it to do its job.

At this point the first question I would have is “do I really have to make 6 classes and 6 interfaces to use MVC?” The answer is “no” since many of them are optional. You certainly need the model, view, and controller classes since they are the core of the pattern. You don’t strictly need interfaces for these, but it will greatly help with unit testing and general flexibility. If you’re trying to cut down, at least make an interface for the view. All of the factories and their interfaces are optional and replaceable by other concepts such as dependency injection (DI) frameworks like StrangeIoC. Even if you decide to keep all 12, it’s easy to make a little script that creates them for you based on template versions.

Another common question is “how can the concepts interact with each other?” This can be done easily by providing multiple models to a single controller. For example, if you want to display the selected enemy on the HUD (a la StarCraft), you could create a SelectionModel and pass it to the EnemyController. When the enemy is clicked, simply set the selection on the SelectionModel and its associated SelectionView will be updated via the SelectionController. Alternatively, you could pass the SelectionModel to EnemyModel and have it set the selection instead of the EnemyController.

The last common question I’ll answer here is “where should I put my logic?” The answer to this comes in the form of the saying “fat models, skinny controllers”. The general idea is to put most of your logic in the models and keep the controllers small. Certainly the views should never do any of the app’s logic.

There are plenty of details and questions that could be asked about this MVC system. If you have any, feel free to ask in the comments. Likewise, let me know if you’re employing MVC in your Unity apps and how your version compares to mine.