A Model-View-Controller (MVC) Pattern for Unity
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.
#1 by RaenirSalazar on September 16th, 2016 ·
For EnemyModel : IEnemyModel script, are you missing “class” in “public EnemyPositionChangedEventArgs : EventArgs”, “event” in “EventHandler OnPositionChanged;”
and in line “public event EventHandler OnPositionChanged = (send, e) => { };” ??
Without these additions, Visual Studio gives various errors. Please let me know if adding these keywords is as I infer, the proper fix.
#2 by jackson on September 16th, 2016 ·
Your fixes are indeed correct so I’ve updated the article with them. Thanks for letting me know!
#3 by RaenirSalazar on September 16th, 2016 ·
Thanks!
Another thing that confused me but luckily my Skype Unity3D group chat came to my rescue was understanding how “Controller” and “Model” still existed in memory, as shouldn’t they have been destroyed coming out of scope in Awake()?
Let me know if this is correct, but because Controller keeps a reference to View, and Model, and since View exists, Controller will continue to exist? And if so, so does Model?
I’m a little confused as to how an object can cling to life in this fashion.
Suppose:
Foo inherits Monobehavior and I instantiate it in my scene.
Boo does not inherit from Monobehavior, and it subscribes to an event from Foo.
This is what keeps Boo alive in memory even if it falls out of scope in the block it was in?
But if we have Coo, and all it has is “myFoo = Foo” it can’t actually use that instead to stay in memory can it?
#4 by jackson on September 17th, 2016 ·
The reference links go like this:
Unity -> View (because the view is on a game object)
View -> Controller (via event subscriptions to instance methods)
Controller -> Model (hard reference)
The only link that’s unclear is the “View -> Controller” one. The reference is made indirectly via the view’s events. The controller needs to subscribe to at least one of them using += to give the event a reference to a delegate like an instance method, lambda, or static method. As long as the subscriber is an instance method, the delegate added to the event will contain a reference to the controller object (i.e.
this
) whose method should be invoked when the event is dispatched. If the subscriber is a lambda that capturesthis
, the delegate will also have a reference. Only if the subscriber is a static function will there be no reference to the controller via the event. In that case you’ll need to make other arrangements so that someone still has a reference to it.I hope that clears things up.
#5 by RaenirSalazar on September 17th, 2016 ·
Thanks! I’m not sure if I completely understand as I mostly come from a C++ background where I never really had a chance to dig into Lambda’s except one time for OpenCL/Cuda/TDD multicore stuff, and delegates I only know of the official example in the Unity tutorials.
One other typo, is I think you’re missing a “private set” in EnemyControllerFactory in “public IEnemycontroller Controller { get; }” this gives an error without the private set.
If I have something like a Context menu that opens when I select a Unit, the scripting and associated logic of SetActive that menu to true, and repositioning it in the canvas to the current location of the Unit, this would go in the View script right? Just making sure I’m understanding the pattern right. I had two classes that discussed design patterns in my university program but they didn’t kinda really give us practical assignments to really use them; just the big assignment at the end as part of a team.
#6 by jackson on September 17th, 2016 ·
Thanks for pointing out yet-another typo. I’ve updated the article with the fix.
As for your unit with a context menu, you’d want the input and output parts to be in the view. That means the unit should detect the click (e.g.
Input
) and display the unit (e.g.MeshRenderer
). All it does with the input is pass it along to the controller via C# events. The only thing it outputs is whatever the controller tells it to output via functions it exposes. The controller decides that a click means to display a context menu. It does this by handling the view’s C# events and calling functions on the view in response. It applies some logic such as computing where the context menu should go, such as above the unit or to its right.The view is “dumb” or “skinny” while the controller is “smart” or “fat”. If you prefer, you can choose to make the controller “dumb” also and put the “smarts” in the model. That’s personal preference though. Just make sure the view is “dumb” and you’re on the right track. You also get to decide how to split up the parts of your app. Should the unit and the context menu be two different models, views, and controllers? Your choice.
#7 by Chom1k on September 17th, 2016 ·
Anyway I had good ideas to handle it, so thanks for confirming it :) Cheers!
@RaenirSalazar what I keep on doing is adding IDisposable to Controller where I handle what should be done to clean things up (unsubscribing/destroying view) and it’s doing it’s job so when time comes to destroy things and cleanup I just call Dispose on the controller and everything is clean.
#8 by RaenirSalazar on September 17th, 2016 ·
I wasn’t worried about making sure they GET destroyed, I was concerned because under my original C++ understanding of references, I thought they SHOULD be destroyed but weren’t because of foul sorcery and/or witchcraft. The idea that an object can hold on to and cling to life as a sort of dark lich using a reference as a phylactery was scary to me.
#9 by jackson on September 17th, 2016 ·
Hah! I assure you, there’s no witchcraft here. :)
I too come from a C++ background and it took me a bit to get used to the whole garbage collection thing. Just think of all the objects in C# as being reference counted. Whenever anyone gets a reference to an object the ref count is incremented. Whenever they lose a reference to the object the ref count gets decremented. When it goes to zero the garbage collector can free the object.
So if a class instance A has a field that’s a reference to a class instance B then B has a ref count of 1 and won’t be collected. If A sets that field to
null
then B’s ref count drops to 0 and it’s eligible for garbage collection.Delegates are just an extension of this. They’re basically function pointers that can handle many different types of functions, similar to
std::function
. Since one type of function is an instance function, they need a reference to the object in order to know which object’s method to invoke. Put another way,obj.Func()
requiresobj
andFunc
. So if A has an event and you add an instance method of B to it then B’s ref count goes up by 1 since A has a reference to it via the event. If B unsubscribes from the event then it cuts the tie and its ref count goes down by 1.I hope that clears things up a bit.
#10 by RaenirSalazar on September 17th, 2016 ·
Just to double check, but, it would generally be a good idea to check for null on the event handler right?
So if (OnClicked != null) OnClicked(this, eventArgs)
Right? This is how most unity examples do it. I mean it seems like we can be reasonable sure that there is a waiter but just in case?
#11 by jackson on September 17th, 2016 ·
There’s no need in this case because a no-op lambda is assigned in the field initializer. Since it’s never removed, the event is never “null”. Alternatively, you can skip the field initializer and add checks for null everywhere you dispatch the event.
#12 by RaenirSalazar on September 17th, 2016 ·
Thanks, one other question.
In the ViewFactory, the initial position of the enemy as instantiated from the prefab may not necessarily match the initial values assigned to the model correct?
But this can be solved by directly assigning the correct values to the view? When the model is created and the initial values assigned, the view and the controller don’t exist yet for these values to be applied right?
#13 by jackson on September 17th, 2016 ·
The view just takes orders from the controller, so it should construct in an uninitialized state and await instructions from the controller. It should never have direct access to the model, so it shouldn’t try to set itself up in some valid state. That would be a huge violation of the MVC paradigm. Instead, your approach is probably the most common one: the controller’s constructor sets up the view according to the model it’s passed. I skipped this for brevity’s sake in the article.
#14 by RaenirSalazar on September 17th, 2016 ·
That makes sense, thank you. What exactly is “common” I don’t really know, you’re the first article that came up. :)
#15 by jackson on September 17th, 2016 ·
MVC isn’t really popular in Unity, so I guess it’s not “common.” :) It just seems like a reasonable place to put the view initialization. Alternatively you could have the view dispatch an event from
Awake
orStart
and initialize the view from an event listener in the controller.#16 by Valerie on October 3rd, 2016 ·
In your article you mention, “it’s easy to make a little script that creates them for you based on template versions”. Just curious if you ever thought about a blog topic for creating the script. I’d certainly be interested in automating this task as I use/create all of the MVC classes, interfaces and factories you describe and find them all to be very useful.
I made it a little easier on myself by creating DummyModel, DummyView, DummyController, DummyFactory and DummyViewFactory. It’s a matter of copying these each time I create a new class. It’s still quite time consuming so I thought I’d see what you had envisioned for this script.
Thanks Jackson!
#17 by Valerie on October 3rd, 2016 ·
…nevermind Jackson, I have created a solution. I use ScriptableObject and create a right-click editor extension menu item. Cheers!
#18 by Valerie on October 5th, 2016 ·
In comment #47 above you mention: “That’s not to say that each view has to directly use the Unity API for graphics and sound rendering. You’re totally free to make classes that the views use to indirectly call the Unity API. For example, PlayerView and EnemyView might both have a reference to an IGameSoundPlayer with functions like PlayFootsteps and PlayDie. …”
I would like to attempt this, but what do you mean “indirectly call the Unity API”?
Thinking I knew what you meant, I created an empty game object in my scene with an Audio Source and GameSoundPlayerView components attached. Here’s where I wasn’t sure if the GameSoundPlayer has references to a model, view, and controller, or if the GameSoundPlayer IS the controller. Maybe you meant just the GameSoundPlayer (acting as the controller) with a view to act as output for sounds and without a model.
I started wondering if my empty game object with the audio source is really “indirectly” calling the Unity API, which is why I’m now asking; in case I’m way off :)
Thanks Jackson!
#19 by jackson on October 5th, 2016 ·
I only mean that those views might use a utility class that accesses the Unity API rather than accessing the Unity API themselves. In this way there’d be a level of indirection between the views and the Unity API. Here’s a little text diagram:
PlayerView
->IGameSoundPlayer
-> Unity APISay you implement the
IGameSoundPlayer
interface with aGameSoundPlayer
class that extendsMonoBehaviour
. You attach theGameSoundPlayer
MonoBehaviour
to someGameObject
and haveGameSoundPlayer
use components likeAudioSource
on theGameObject
to play sounds.Save this as a prefab and
Instantiate
it. Get theIGameSoundPlayer
component off of theGameObject
that got instantiated. Now give theIGameSoundPlayer
to your view classes such asPlayerView
andEnemyView
. The view classes are each attached to aGameObject
that doesn’t have sound components like anAudioSource
. They don’t contain any code that deals with anAudioSource
. Instead, they play sound by calling the methods of theIGameSoundPlayer
they’ve been given.In this way you have a reusable sound player and your views aren’t directly calling the Unity API. There are plenty of pros and cons to this approach, but it’s one you could take if you expect to share significant Unity API code between multiple views. Lastly, a variant of the above is to use the same types but attach them all to a single
GameObject
.Hope that clears things up.
#20 by Kamil on October 11th, 2016 ·
Hey, nice article. I’m currently building new game using this pattern. I can see long term benefits from using this architecture.
In your example in EnemyController you are setting model.Position. At first I thought you could set here view.Position aswell but I figured it’s better solution if you want to change object position from somewhere else and you have access to Model only from there, also it’s less problematic because you don’t have to set both model.Position and veiw.Position everytime.
I have some concerns about it. What stop you from sending event OnPositionChanged also from the View and not from the Model? Should I have all properties(like position, rotation, velocity… all properties that are modifiable) in the Model equivalent to properties in the View and in the Controller methods work directly only on Model properties and View properties update only by syncing Model and View properties? What is the best practice for syncing properties in View and Model?
#21 by Kamil on October 11th, 2016 ·
I also have a problem with getting Models during runtime from Unity events like OnTriggerEnter. Lets say I have a tank with a Collider and it is looking for enemy tanks with OnTriggerEnter. Then if it got any enemies in range it fires a bullet. In my MVC it looks like this.
TankView
TankController
I saw in comment section similar problem and you answerd(#120 comment) that she/he should pass Model instead of View. Getting references to other Models during runtime seems impossible. The only solution is to pass the ModelA to ModelB when creating ModelB. In my example I could get position from the TankView but what if want to get some properties from the TankModel that I don’t have in the TankView? Am I missing something?
Some explanation from you would really help.
#22 by jackson on October 11th, 2016 ·
You have a few questions, so I’ll take them one at a time.
Q: Shouldn’t
EnemyController
set the position of the view too?A: It does, albeit indirectly. It sets the position on the model which dispatches an event when the controller handles by setting the view’s position.
Q: Should the view dispatch
OnPositionChanged
too?A: No, the model is the “source of truth” regarding where the enemy is. The view is simply told the position to be at and it complies by moving there. Look to the model for the data representation of the object.
Q: Do view properties update only by synching with model properties?
A: Yes, the view is the output of the model’s data. It receives orders from the model via the controller, who acts as a middle man.
Q: What’s the best practice to sync the model and view?
A: The controller reads properties and handles events from the model and sets properties on the view to output the new state. It also handles events from the view and passes it to the model to handle input like button presses.
Q: How can my tank find another tank?
A: Either your tank controller or your tank model needs a reference to the other tank models. If tanks are coming and going during the game, consider a model for the game scenario that has a list of tank models on it. Once you have that it’s a lot easier to do the logic you describe. The tank view gets the
OnTriggerEnter2D
call and dispatches an event. The tank controller handles the event by looking through theScenarioModel.Tanks
list for aTankModel
whosePosition
property is within range of the tank controller’s ownTankModel.Position
property. Then you can spawn your projectile and proceed from there.Thanks for the detailed post and questions. It sounds like you’re well on your way to getting MVC working in your game!
#23 by Kamil on October 12th, 2016 ·
Thanks for clearing things up. I’ve read your article few times but I wasn’t sure. It’s something new to me but I’m getting it.
Btw it’s really admirable how you are handling all of the questions even on older articles. Also do you have in plans writing an article about unit tests that can be applied to this pattern?
Cheers!
#24 by jackson on October 12th, 2016 ·
I hadn’t planned on that, but it sounds like a good idea for a followup article to this one. I’ll make a note and maybe you’ll see an article soon. :)
#25 by Danie on January 28th, 2017 ·
Hi there first off I just want to say thanks for this article, it’s been very helpful. I’m still new to Unity, so I’m having a little trouble discerning the implications of using these models for game logic and how it relates to the unity api.
For example, if some aspect of the model gets affected by physics, where do you place the functionality? In the view, or the controller? Also, what steps would I need to take if I want to update a model off-screen in this situation?
#26 by jackson on January 28th, 2017 ·
You’re welcome! I’m glad you’ve found this article helpful.
As to your question, where to keep the data representation can be pretty tricky with the MVC pattern described in this article. Some data like the player’s health or the number of bullets available can easily be stored in the Model. Other data like the player’s position and velocity is already stored in the View by Unity. One approach is to duplicate it in the Model and try to keep it in sync via the Controller. Another is to make the View into a Model-View, as I described in this article. A hybrid where some data (e.g. health) is in the Model and some data (e.g. position) is in the Model-View is also possible.
In the end it’s up to you and the requirements of your own project. There’s no one right answer, but these are three options and I hope at least one of them is helpful in your situation.
#27 by Naweed on September 10th, 2017 ·
hello , i have a question.
is it really necessary to have a model, view , controller for each entity?
like : if i have 50 entities , do i really have to make 3 classes for each one of them?
it seems like a bad practice.
i have a method that i’m using in android apps i make where , i have a view(controls all view objects across all activities) , i have a services class(which has all api calls , and basically any low level code in it), and i have a service repository(which directs each view to it’s corressponding required service) .
my method is basically the MVC itself , but i think i’ve saved a lot of pain of making dozens of classes , by putting all functions in one class.
by the way i’m no stranger to unity , i know for a fact that my method could easily be used in unity games with adding DI to it .
so i ask again , is it really necessary to tolerate the pain of making too much classes , or the game will be fine using my approach , and having functions for all entities in one place?
thanks a lot.
#28 by jackson on September 10th, 2017 ·
I think it’s best to view MVC as a tool. You could make a model, view, and controller class for each kind of entity in your game, but you don’t have to. Perhaps you’d prefer an ECS instead. You can take the MVC pattern and apply it where it makes sense to you. That might mean just in the GUI where MVCs are traditionally used and that would be fine. In the end you’ll have many possible applications of MVC that you’ll have to decide based on the specifics of your game, team, resources and so on whether MVC is the right tool for the job.
#29 by Dogukan on November 6th, 2017 ·
Hey Jackson, It’s good that you still take care of this article! There are couple of questions that I would like to ask. First of all, does having input control within the EnemyView is good idea? If we have 100 instances of enemies, wouldn’t that piece of code just send raycast to same place 100 times per frame?
This was something bothering my mind. Now to my questions. I’m thinking about the unity implementation itself. So in your example you have a “MainScript”. What’s its purpose in real life? The most convenient role that I can think of for this script is the EnemyManager one. or “Entity”. Which is the main hub that commands controller to do stuff? In hierarchy I would think it as a GameObject called “EnemyEntity” and as a child of it there is “EnemyView” or “Enemy” that you’ve called in your article. If that’s the case, this is more than just an MVC pattern, right? Perhaps MVC-E(ntity) pattern? Because there is another script came to the equation, called “MainScript”. What do you think about it?
Also what do you think of using both EC and MVC in game? I think some parts of the game can be created better with EC and some parts can be better with MVC. For example an inventory system would be created better with MVC, while and item system would be better with EC. What do you think?
#30 by jackson on November 6th, 2017 ·
Hey Dogukan, these are some great questions. Let me take them one-by-one:
Q: Should input handling be in the view?
A: In this article’s formulation of MVC, the view is responsible for input and output. This certainly isn’t the “one true way” to do MVC though, so feel free to make a variant where the view only handles output. Or a variant where it only handles some input, like
OnCollisionEnter
, and other input like raycasting is handled elsewhere. That may be warranted for performance reasons in some cases.Q: What is
MainScript
for?A: It’s a script to run when the game starts. It starts up the MVC system and then pretty much goes dormant. Think of it as bootstrapping code, like a “main” function.
Q: What commands the controller?
A: Sometimes the controller is reacting to input from the view, but sometimes it’s running commands from outside the MVC. That can be whatever code you want it to be. Remember that the MVC doesn’t have to be the only way you write code. You can use it for just a part of your game and the other parts can give it commands.
Q: How does this look in the hierarchy?
A: Views can be in the hierarchy however you want them to be. In the article I just put them at the root level for simplicity. Feel free to slot them into your hierarchy wherever they make sense. Go ahead and attach more components to them if that makes sense. There’s really no restriction here.
Q: What do you think of using both EC and MVC in the same game?
A: There’s nothing about this MVC design that would exclude you from using other systems like EC or ECS in the same game. You might use MVC for your UI and EC for your enemies and that’s just fine. Choose the right tool for the job.
#31 by Mike Vargas on November 7th, 2017 ·
Dang, we would do well on the same team. This is uncannily similar to how I approach MVC in Unity and it’s very nice to have that approach validated.
#32 by Mike Vargas on November 7th, 2017 ·
One question I forgot to ask:
Though you and I approach events in the same way, do you ever run into limitations with using C#’s built-in event system as opposed to a custom event dispatcher / message system?
#33 by jackson on November 7th, 2017 ·
Yes, I’m often not a fan of the built-in events system for various reasons. Sometimes I use alternatives to work around some of the issues I have with them. I’ve written various articles about C# events over the years if you want to read more of my thoughts on the topic.
#34 by Mike Vargas on November 12th, 2017 ·
How do you handle situations with nested hierarchies of things that could each have a model, view, and controller?
For example, consider a board game which has tiles, and each tile could have different “components” (like maybe it has a key or a door, which can change depending on player interaction).
My inclination is to create a Board object which has a 2D array of Tile objects as children, and each Tile object contains a list of TileComponent objects. This is all model territory, though.
So if I instead create MVC objects, presumably I’ll create a BoardController. But will this contain a list of TileControllers, each managing their tiles? And will each TileController contain a list of TileComponentControllers, each managing their own TileComponents?
Will I still have a hierarchical Board model (containing Tiles which contain TileComponents), and just point to these from a separate hierarchy of Controller objects?
Sorry if this is confusing. I guess another way to phrase it is: Do I replace my hierarchical Model objectswith hierarchical Controller classes? Or perhaps do I keep the hierarchical Model objects and just point to them from a separate hierarchical Controller object tree?
#35 by jackson on November 12th, 2017 ·
Models are just plain classes. That means you can have models that contain other models. In your case, you could have
BoardModel
containing a collection (e.g. 2D array) ofTileModel
which in turn contain a collection (e.g. 1D array) ofTileComponentModel
. Then you create controllers and views for each of these models, as normal. If you want, your views’ game objects can be nested in the Unity hierarchy to match the nesting in theBoardModel
, or you can flatten them out instead.Assuming you built a system like this, let’s step through what would happen if you wanted to have an effect at the board level. Say every turn of your game a bonus is dropped on a random tile for players to try to pick up. You might have a
BoardModel.DropBonus
that chooses a random tile (X, Y) coordinate and callsTileModel.DropBonus
on that tile from its 2D array.TileModel.DropBonus
would then create a newTileComponent
model, view, and controller (optionally with a factory) to represent the bonus and add to its 1D array of components.#36 by Mike Vargas on November 12th, 2017 ·
I like the way you approach this. But when TileModel.DropBonus created the model, view, and controller objects for the bonus component, where would I store the created Controller object? Would TileModel have a reference to all of its created ComponentController objects, or would its corresponding TileController hold these?
I really appreciate you helping me out with this, I’ve struggled with this sort of design problem for years.
#37 by jackson on November 12th, 2017 ·
It takes some work to wrap your head around MVC in Unity. It’s not the “normal” way by a long shot, so there aren’t a lot of examples to help you. I’m happy to answer questions though. :)
In this article’s formulation of MVC, the model and view don’t have references to each other or the controller. The controller has references to the model and view and serves to coordinate them. So you wouldn’t have
BoardModel
,TileModel
, orTileComponentModel
keep references to any controllers.In the example shown in the article,
MainScript.Awake
creates the model, view, and controller but then abandons its references to all of them. That’s OK because other references keep these objects from getting garbage-collected. The view is aMonoBehaviour
on a game object, so Unity keeps it around. It has an event that the controller has subscribed to, so the event’s delegate has a reference to the controller which keeps the controller around. The controller has a reference to the model, so the model gets kept around too.So while you can keep explicit references to your controllers, there’s no need to. In this case,
DropBonus
can just abandon the references to the model, view, and controllers it creates just likeMainScript.Awake
did.#38 by Mike Vargas on November 19th, 2017 ·
Cool, yeah I’m coming to grips with abandoning the references and trusting that they’ll stick around. :)
I noticed that your factories employ Resources.Load. Do you see any issue with having factories instead be MonoBehaviour subclasses so that they can take advantage of editor support, namely the ability to instantiate a prefab linked to the script? It feels a little abnormal but also seems to work well with Unity’s flow, while still maintaining the MVC pattern.
#39 by jackson on November 20th, 2017 ·
I only used
Resources.Load
in the article for the sake of simplicity. Feel free to build your scene however you like. It’s largely unrelated to MVC anyhow, so you can use whatever suits your game best.#40 by Mhmd gh on October 15th, 2020 ·
Thank you, what a great article!, this is the best article that explain mvc with unity I found so far.
#41 by Laurentiu Ganta on October 19th, 2020 ·
Would there be a direct reference in between classes of the View layer or will the communication be handled by the controller?
For example, a RecipeView class that displays a list of ingredients in IngredientView. Will the RecipeView have a List?
#42 by jackson on October 20th, 2020 ·
There are many valid approaches here. Here are two:
RecipeView
has multiple nestedIngredientView
that it uses to display the ingredientsRecipeController
has multipleIngredientView
and optionally also aRecipeView