Recently I was answering questions on libGDX on StackExchange, and I got a really interesting question about using the Model-View-Controller (MVC) design pattern. I’ve been experimenting with it in one of my own games recently, so I thought I would write a brief guide on how to employ it (specifically within the libGDX framework). By the end of this post, you will have learned:
- What MVC is
- Why MVC is awesome
- How to use MVC (specifically with libGDX)
So what is MVC?
Model-View-Controller (MVC) is a popular way of organizing code where we split our program into three logical pieces: 1
- The Model is the code that represents our game logic and data
- The View is the code which renders the model (or a portion of the model) on the screen and collects the player’s input
- The Controller is the routing code which manipulates the model and stitches things together
Each of these three parts relate to each other in specific ways:
- The Model is read by the View and updated by the Controller.
- The View reads the Model and provides input to the Controller.
- The Controller determines which View to show and owns all updates to the Model.
This can all be summed up by the following picture:
MVC in Motion: An Example
Let’s pretend we were building a PAC-MAN clone and structuring it using MVC:
First, the player opens our game. libGDX launches our Controller, which tells the View to display a menu screen. The View renders this screen and binds the “New Game” button to the Controller.
Next, the player clicks the “New Game” button. The View notifies the controller per the binding that occured earlier. The Controller instructs the Model to configure itself for a new game and changes the View to be the “Maze” screen. The new View queries the model to figure out what it should paint on the screen.
Going forward, our controller will then update the model each frame with the amount of time that has transpired and an abstract representation of the player’s input. The model would update the position of PAC-MAN and the ghosts based on this information. Finally, the View would query the model and redraw the screen with PAC-MAN and the ghosts at their new positions.
Now at this point I should mention that this is my definition of MVC. I don’t want you to feel swindled if you find that your school or workplace has a different one. The truth is that for all its popularity, there are a lot of nuances to MVC that folks don’t seem to agree on. Wikipedia, Microsoft, Apple, and CodingHorror all definite it slightly differently, and that’s just scratching the surface. If you’re not careful you can lose an afternoon reading different blogs explaining what MVC is and why the other blogs are wrong. At the end of the day, just remember the wise words of one our favorite pirate captains:
I recommend you treat MVC more like a guideline than a rule- if you use it to structure your code, there are going to be a bunch of cool things you can do (as we’ll talk about next). That said, it’s not a silver bullet. 2 You’re going to find in some cases having the model push events to the current view is a lot cleaner than trying to have the view poll the model. Or perhaps you’re going to be stuck with a legacy code base or 3rd party library which doesn’t give a rip about MVC.
Why MVC is Awesome
Even if it’s not a silver bullet, MVC is still pretty cool for several reasons:
Models Let us Test
Testing sounds boring- isn’t that what play-testers are for? But if your game has any complex algorithms (such as the AI uses to determine if the player’s trade is “fair” or not), it really helps to have unit tests you can run against that algorithm to make sure it works as expected. If someone tweaks the algorithm, you’ll know if it breaks the game immediately instead of a week later.
This sort of unit testing is difficult to do if you’ve mixed your game logic into the View or Controller because in order to create a lot of the libGDX objects, you have to initialize the OpenGL context. This makes your tests take a lot longer, and when they break you’re never sure if what broke was the portion of your code you were trying to test or not. There are tricks you can use such as “Headless” contexts, but these still add to the testing overhead.
If however all your game logic is in the model, and your model doesn’t have any of the graphics-related View code in it, you can spin up a model independent of libGDX and run your unit tests against it. This is a lifesaver in medium-to-large games.
Views let us Port
While libGDX promises (and delivers) to let your port your game between Desktop, Android, iOS, and Web platforms, that doesn’t always mean your UI will port nicely. Console games are built for game controllers with those funny little knobs. Most desktop games expect little mouse cursors instead of fat fingers. Often it makes sense to provide a different UI layout based on the platform.
If you mix your View code with your Model or Controller, adding a new layout likely means refactoring across the entire code base. If we’ve isolated our View code from everything else, then the chances of us breaking things during a port are much lower.
Controllers let us Expand
Since the Controller decouples model updates from the View, it lays the foundation for use cases where you might remove the View altogether. In addition to writing unit tests against the controller (as mentioned earlier), you can also start writing AI and network clients that interact with your game through the same interface as your view.
MVC in libGDX
Enough theory! Let’s see how this works in libGDX.
When I create a model in libGDX, I try to separate it into two parts:
This is where your game-specific stateful information goes. One really important part of MVC is making sure the stateful information is captured inside the Model and doesn’t leak out into the View or Controller, because if you keep it inside the model, saving and loading your game becomes as simple as (de)serializing your model. Due to the nature of Game Data, these classes will almost exclusively be your own, aside from some of the libGDX utility classes or datastructures (Array, ObjectIntMap).
Since the model owns the Game Data, it is responsible for owning the interface used by the view to query data and the interface used by the controller to make changes to the model. This means being able to interpret the controller’s commands.
It’s also important to note that the Game Data classes are not Plain Old Java Objects (POJOs)- they contain game logic as well. If I build a MerchantNPC class, it is responsible for storing information about the merchant (e.g. what items it has for sale) as well as the algorithm it uses to determine the price. Naturally you can use good OOP to decompose something like this into several smaller classes, but my point is that they would all fall under the Model umbrella.
libGDX already provides an excellent AssetManager class for loading resources such as meshes and textures, and it doesn’t make sense to rebuild this information. However, I do try to separate the “complex” libGDX objects such as Textures from my Game Data. For example, if an NPC needs a portrait, my NPC Game Data class will contain a string with the portrait name instead of a reference to the specific texture. This is probably the most contentious thing I am recommending, but it comes with some really cool benefits:
- It becomes a lot easier to serialize the Game Data. You can serialize strings with little to no effort; if your Game Data class contains textures, you have to add extra logic during (de)serialization to teach your game how to serialize the texture reference and then re-attach it later when you deserialize.
- It becomes easier to test in an environment where you don’t have an OpenGL context to do rendering (the main example being a server or unit tests). Textures come with hidden dependencies that we don’t want to mess around with.
- The Model doesn’t know anything about how your game world is rendered. Changing textures becomes as simple as changing a string in a JSON file.
- In theory 3your model becomes decoupled from libGDX and it becomes a lot easier to port it to a different Java-based library should the need arise.
I use the Screen interface from libGDX as the basis for my views. Common setup goes into an abstract View class, and then child classes build the specific layouts required. Each View gets a reference to the Model (for reading what to draw) and the Controller (for making changes to the model or doing things). Beyond that, there are a few interesting things to note:
Views are Stateless
Views don’t contain any stateful information. They get state from the Model (or in a few cases like checking to see if “debug mode” is enabled, the Controller). This has a bunch of benefits:
- We can load/save the game by de/serializing the model as discussed earlier. If stateful information was in the View, we’d have to serialize it as well.
- We can create/destroy views safely without changing anything in our game. I’ve found this helps avoid a lot of weird bugs.
- We don’t accidentally make our Model or Controller dependent on the View to work; this allows us to create unit tests without mocking a view.
The implication of stateless views is that we can now think of them more like visual filters- their sole purpose is to render part of the model.
Each of my views/screens follows a certain lifecycle:
First, the constructor builds any static content (i.e. content that isn’t dependent on the model). This is typically done once per type of screen. After all, if views are just visual filters pointed at the same model, it doesn’t make sense to have more than one of the same type.
show() method (re)builds any content that changes based on the model state. Examples would be labels that change based on player location, or buttons which are only shown at certain points in the game. If your screen isn’t doing any complex rendering (e.g. a menu screen or a save/load screen), it’s quite easy to use a single Stage for the UI that is rendered by the abstract View class- in these cases the implementation class only needs the constructor and
render() reads the model data and paints the view’s portion on the screen. The view is responsible for knowing what it needs to draw.
An truncated example of the base screen and actual game screen can be seen below:
Now when I build a controller in libGDX, I give it several responsibilities:
Lord of the Lifecycle
First, it is responsible for orchestrating the game’s lifecycle. When the game is started, paused, resumed, and exited, it’s the controller’s job to tell the model and view what they need to do. Thus, in libGDX, my controller generally extends the Game class.
Guardian of the Model
It’s the interface to the model. Think of your controller as the common interface between your view and your model (at least when it comes to writes). It interprets input captured in the view into standard commands. If one player uses a touch screen, another uses a gamepad, and a third uses a mouse, their input is going to be captured differently, but ultimately my game logic is going to be the same and we will manipulate the model in the same way. The cool thing about this model is that once this interface is robust, it’s possible to start supporting use cases where you don’t have a view at all such as unit testing, AI4, or network peers.
libGDX already provides a fairly robust interface you can use to abstract your player’s input method with InputListener, but you can also have your controller take this a step further and use the Command Pattern to communicate with the Model.
The third responsibility that I assign to the controller is routing the player to different screens. There’s a certain amount of tear-down and set-up that needs to occur when the player transitions from one screen to another, and in order to keep the screens independent of each other (remember SoC!) I have the controller manage the lifecycle of each screen. As you saw when we discussed Views, each screen is still responsible for knowing what it needs to set up and tear down, but depends on the Controller to know when to act.
Abstractor of the OS
Finally, I use the Controller to abstract the Model from the underlying OS. The way files are manipulated can sometimes differ depending on whether we’re on a phone or a PC. The Game Logic and the Game Data don’t change, so I don’t want my model to change. This can be thought of as an extension of the Controller’s responsibility to govern interactions with the Model.
Hopefully this description is helpful! If you have any questions, thoughts, or suggestions on how to improve this model, feel free to add a comment below! You can also subscribe at the bottom of the page if you’d like to be notified of future posts.
- See Wikipedia for a good definition of MVC ↩
- Also, if someone at your workplace dares utter the phrase “MVC compliance”, slap them for me ↩
- In practice, it is extremely helpful to use libGDX utilities or data structures such as the Array or ObjectIntMap classes in your model classes, but these are relatively easy to refactor out of the code base ↩
- Dr. Kimberly Voll has a really interesting GDC talk about how she used the controller interface to write her AI ↩