Articles: MVC in games
by Roy Triesscheijn
MVC Primer
MVC stands for "Model View Controller" and has been an architectural pattern in software engineering for quite some time now. MVC allows decoupling between what 'the program is supposed to do' and how this is made visible and controlled.
In MVC the three main responsibilities of the application are handled by three separate parts:
- The model houses the actual business logic. The model is totally decoupled from the controller and view.
- The view observes the information from the model, and if needed request an update of the information. Data from the model is lightly massaged, formatted and then presented.
- The controller controls the application by mapping different kinds of input to public methods available on the model. The model itself always has the final responsibility of doing something with the request made by the controller. In many form-based applications the view and controller are hard to distinguish from each other.
Using MVC will allow you to reuse your complex model in different scenarios. Want to prepare your program for a different kind of input? Just write a new controller. Want to visualize your data in another way, just write a new viewer. In a good application controllers and viewers can even be changed while the application is running.
Relevance in games
All the normal pros of MVC apply to game development. When creating games using the XNA Framework the ability to easily change the view and controller of your game becomes incredibly useful. I will demonstrate this by giving an example for the Model, View and Controller of an imaginary game that is being developed for the Xbox360, PC and WP7.
Model
When you develop a game the game mechanics will be stored in the model of the game. This same model can be used on the Xbox360, WP7 and the PC. When you write a game you will always have to write some sort of model. The only thing to take extra care of is to keep it decoupled from the view and controller. Never should the model handle some sort of input or put some restraint on how something is displayed on screen. This might take a bit getting used to, but the benefits will come clear when visiting the view and controller.
View
Because we want our game to work on platforms with different capabilities, we develop different views for each of them. The WP7 view can't have shaders for example, and should have a layout optimized for a small screen with a 480x800 resolution. So we choose to limit the amount of statistics the user sees and just show the gameplay and a score.
However on the 360 and PC we have plenty of screen estate, so we choose a different layout, show off the statistics, use smaller fonts, and we use shaders to make everything look extra crisp. The model is already in place and runs on all platforms so we don't have to do any work on it. The only 'per-platform' work we have to do (so far) is the visualization. This saves us a lot of time.
Controller
Well let's continue and take a look at the controller. Again there are big differences; the Xbox uses a gamepad, where WP7 uses a multi-touch screen, the PC uses a mouse and keyboard. You can see again that it is useful to have the model completely separated from this. And to have specific controllers take care of these different scenarios. We will now have to write a class for each type of input that converts raw input (a button pressed, or a mouse gesture) to an abstract input, for example "Input.Left". Then a second controller should convert this abstract input into a request for an action based on the model's state.
Let's take the controls of a camera as an example. On a PC I would press the 'up'-key on my keyboard this would be converted to the abstract input "Input.Up". This would be delegated to the controller that is specific to the current state, which then translates "Input.Up" to a request to the model of the camera to look up. (Note that the model will always determine itself if it looks up according to its business rules, the up direction could be blocked or another business rule could prevent the action).
On an Xbox360 the first step is different. I would nudge my thumb stick up. The appropriate input controller would again convert this to appropriate abstract input "input.Up". The rest is the same as with the keyboard example.
Solution in code
Now we've talked about benefits of MVC in games and common problems which happen when not using MVC, it would be an appropriate time to show a solution to this problem.
For this example I've written a very small Pong game using the principles described above. The most important classes, as well a tiny bit of extra commentary are listed below. You can download the full sample here.
Let's start with the model. Let's create an abstract model and then a model for the paddles/players.
public abstract class Model
{
public Model(World world)
{
this.world = world;
}
public abstract void Update(float elapsedSeconds);
#region StateVariables
public virtual Vector2 Position{ get; set; }
protected World world;
#endregion
}
public class PlayerModel : Model
{
/// <summary>
/// Creates a new player in the world, specify everything in world coordinates
/// not screen coordinates.
/// </summary>
/// <param name="startPosition">top left position</param>
public PlayerModel(World world, int width, int height, Vector2 startPosition)
: base(world)
{
this.width = width;
this.height = height;
this.Position = startPosition;
}
public override void Update(float elapsedSeconds){}
#region MethodsForController
public void Influence(PlayerAction action, float elapsedSeconds)
{
switch (action)
{
case PlayerAction.MoveUp:
Influence_MoveUp(elapsedSeconds);
break;
case PlayerAction.MoveDown:
Influence_MoveDown(elapsedSeconds);
break;
}
}
private void Influence_MoveUp(float elapsedSeconds)
{
Position = new Vector2(Position.X, Position.Y - (speed * elapsedSeconds));
if (Position.Y < 0 )
{
Position = new Vector2(Position.X, 0);
}
}
private void Influence_MoveDown(float elapsedSeconds)
{
Position = new Vector2(Position.X, Position.Y + (speed * elapsedSeconds));
if (height + Position.Y > world.boundaries.Height)
{
Position = new Vector2(Position.X, world.boundaries.Height - height);
}
}
#endregion
#region StateVariables
public int width;
public int height;
public float speed = 90;
#endregion
}
public enum PlayerAction { Nothing, MoveUp, MoveDown };
By creating a method "Influence" I've made it very explicit here that you always request something from the model, it's never sure if the player will actually move up or down. This depends on the business rules.
Now there is of course also a ball, but I lets you find it in the accompanied source code. Let's take a look at the viewer for our PlayerModel.
public class PlayerViewer : Viewer
{
public PlayerViewer(PlayerModel player, Texture2D texture)
{
this.player = player;
this.texture = texture;
}
public override void Draw(SpriteBatch spriteBatch)
{
Rectangle playerRect = new Rectangle((int)player.Position.X, (int)player.Position.Y, player.width, player.height);
spriteBatch.Draw(texture, playerRect, Color.White);
}
private Texture2D texture;
private PlayerModel player;
}
There is nothing difficult here as you can see. We just draw a simple texture at the position of the player. Note that in real-world applications the model and view could be using two different coordinate sets, so you will often have to convert between 'model-space' and 'screen-space' in a viewer.
Now for our controllers there is a large class (called InputManager) that converts raw input (as described in the controller section) to an abstract input represented by the following enumeration:
public enum Inputs
{
A, B, X, Y, Left, Right, Up, Down, Start, Back
}
This class and the abstract inputs are then used in the controller of our player model.
public class PlayerController : ModelController
{
public PlayerController(PlayerModel player, InputType type, PlayerIndex index)
{
this.player = player;
manager = new InputManager(type, index);
keyMap = new Dictionary<PlayerAction, Inputs>();
//normally fill the keyMap with user settings.
keyMap.Add(PlayerAction.MoveUp, Inputs.Up);
keyMap.Add(PlayerAction.MoveDown, Inputs.Down);
}
public override void Control(float elapsedSeconds)
{
manager.Update();
if(manager.IsInputDown(keyMap[PlayerAction.MoveUp]))
{
player.Influence(PlayerAction.MoveUp, elapsedSeconds);
}
else if (manager.IsInputDown(keyMap[PlayerAction.MoveDown]))
{
player.Influence(PlayerAction.MoveDown, elapsedSeconds);
}
}
private InputManager manager;
private PlayerModel player;
private Dictionary<PlayerAction, Inputs> keyMap;
}
As you can see we map the different actions that are possible to different abstract inputs, which themselves are matched to different raw inputs. These layers of abstraction are very useful, maybe also in ways that you haven't imagined yet. For example how about a controller that doesn't read keyboard, mouse, gamepad or gesture input at all?
Take a look at the AIPlayerController, which will probably beat you!
public class AIPlayerController : ModelController
{
public AIPlayerController(PlayerModel player, World world)
{
this.player = player;
this.world = world;
}
public override void Control(float elapsedSeconds)
{
float playerCenterY = player.Position.Y + (player.height / 2);
float ballCenterY = world.ball.Position.Y + (world.ball.height / 2);
float diff = Math.Abs(playerCenterY - ballCenterY);
if (diff > 20)
{
if (playerCenterY > ballCenterY)
{
player.Influence(PlayerAction.MoveUp, elapsedSeconds);
}
else if (playerCenterY < ballCenterY)
{
player.Influence(PlayerAction.MoveDown, elapsedSeconds);
}
}
}
private PlayerModel player;
private World world;
}
So, download the code, play a little write a couple of different views and controllers and maybe expand the model so that you really understand the power of MVC in games!
Conclusion
I hope by now the usefulness of MVC in games has become quite clear. I think the benefit of MVC, especially when targeting such diverse devices as PC/Xbox360 and WP7 is huge, this could really save you a lot of time.
The given source code, tiny as it is, should give you some idea on how to implement your own way of doing MVC in your games. Please note that when using MVC there are plenty of ways for you to exploit the usefulness of inheritance, encapsulation, interfaces, abstract class and the likes. I have not done this much because I wanted to keep the sample code as small as possible but for slightly larger project you will definitely benefit from exploiting this.
Further reading
I hope you've enjoyed this tutorial. But there is more:
For an in depth article of MVC see:
http://en.wikipedia.org/wiki/Model–View–Controller
For notes on the input manager that converts gamepad and keyboard input to abstract input, see: http://roy-t.nl/index.php/2010/11/05/codesnippet-handy-input-manager/
For more interesting tutorials see:
http://roy-t.nl (my personal blog)
http://www.sgtconker.com (great website with all sorts of game development related tutorials, including some of my own).
December 5th, 2010 - 23:03
Hi Roy,
Nice article, one of the best explanations I’ve read and much easier to understand than the Wikipedia entry. I think making things concete with examples for XNA developers makes it more easy to pick up.
One question though.
What is the recommended way to cope with situations when the model must change according to the view being used? For example my WP7 game needs much simpler physics, fewer particles, and possibly simpler level design compared with the Windows of Xbox builds.
December 6th, 2010 - 15:27
Hi Roy,
thanks for this nice and concise hands-on article. It just fits in our current project so that my students can map your example to their requirements.
Robert
December 7th, 2010 - 13:14
While this is truly a great idea I do see some problems when it comes to multi-platform development. The smaller display on a WP and its controls (touchscreen) differ very much from those on the console or the PC.
And this should be taken into account when designing a game for each platform – it is not advisable to leave the M in MVC untouched when coping with this problem. Often it is a good idea to adapt or abandon certain gameplay mechanics in order to port a game to a specific platform. Just my 2 cents. Overall though I agree with your conclusions.
January 1st, 2011 - 21:14
Hey Beringela, Robert and Marco Sperling. Sorry my response took so long (I blame the holiday season). Thanks for your kind words!
@Beringela
Once you need to change your model (because that is where the physics should be) depending on the target platform, then your game might change so much that it has indeed become another game. One way to deal with this is to have a separate common-engine project that can handle everything that is relevant to all platforms (UI-logic, scoring? etc..). Always try to maximise code-reuse, but try not to make things too complicated, favor simplicity and a little extra work over uber-scalable-complex code.