Game Architecture: Model-View-Controller
When programming a new game, most of the time you implement some basic features and start from there. As the game gets bigger, your code gets more interwoven, and the classes bigger. And before you know it you end up with spaghetti code and god classes, and that’s the last thing anyone wants.
Let’s assume we’re programming a racing game, and we have a class called RaceCar. Soon enough that class will contain a method to update it’s state, to draw it onto the screen, to accept user input, etc. It will become huge with all kinds of different functionality in there. So how can we divide up our game so it’s nicely split up into modules and classes? Just read on and learn ;).
Step 1: Split up the game into logic and rendering
The first thing we should do is decouple the rendering from the logic. And this makes perfect sense. If you take look at my game loop article, we can even let the game logic run at a different speed than the rendering.
The game logic will know nothing about the rendering, so it doesn’t matter if it will be displayed in 2D, 3D, ASCII art or whatever. The rendering however depends on the logic, because it needs info on how/where to display everything. The following figure shows the 2 modules with their dependencies. The rendering happens inside the “View” module.
_________ _________ | | | | | Logic |<-------| View | |_________| |_________|
What this means in practice is that we will create a RaceCarView class next to our RaceCar. RaceCar handles the logic and user input, and knows nothing on how to display it, and RaceCarView displays the race car on the screen, using information from RaceCar.
Problem with interaction
When our car is controlled by a joystick or keyboard this concept works out just fine. But assume we want to mouse click on an opponent car to take over its control. If our logic knows nothing about the view, we don’t know on which car the user clicked. So in this case our logic also needs to query the view, which creates a circular dependency, and we don’t want this. Lucky for use we can get rid of this by doing another split, explained in the following section.
Step 2: Split up the logic into model and controller
The second thing we can do is further split up the logic into a model and a controller. The model is basically the game world, it knows nothing about displaying, user input, etc. It just implements all the world rules, and how entities interact with each other. The controller knows about the model and can manipulate it. For example the controller checks the user input, and manipulates the car accordingly. As we saw in the previous section, the controller needs to query the view when using mouse input for manipulating on-screen objects. And thanks to the split up, the model doesn’t have any dependencies like the logic had.
________________ | | | Controller | |________________| | | | | | | ______V__ __V______ | | | | | Model |<-----| View | |_________| |_________|
In our RaceCar example, we will extract our user input handling into a new class RaceCarController. When the user presses the left-button, the controller sees this and calls the model’s RaceCar.steer_left(). The model then handles the world rules of the car going to the left.
Where to put the AI?
Most games use an AI to control certain game objects, so the question is where to implement this, in the model or the controller? The answer is pretty simple: if the AI controls the same object in the same manner as the user does (i.e. a ‘bot’), implement it in the controller. In the other case, it’s part of the game world so you should implement it in the model. In our example the car AI’s should be implemented in the controller, because they are basically bots. If we would have pedestrians that jump out of the way, they would be implemented in the model, because they are part of the world.
So after dividing our code up we get 3 separate modules: the model, the view and the controller.
- All the rules of the game world are implemented in the model, and it also contains the state data of every game object or entity. It is a pure game world simulation, so it doesn’t know anything about user input or displaying to a screen.
- Rendering to the screen is handled by the view. It uses the model to know where to draw everything. The view doesn’t have any other functionality than this.
- The controller handles the user input and manipulates the model. First it checks for user input, then it might query the view to see which on-screen objects are being clicked by the mouse, and finally it manipulates the model. Multiple controllers can be implemented, for example a keyboard controller, joypad controller, and even AI ‘bot’ controllers.
The model is standing on it’s own and doesn’t need to know anything about the others. The view depends only on the model to render everything on the screen. The controller receives input, can gain additional information by calling the view, and at the end manipulates the model. The advantages of this architecture:
- Nice modular design
- Game world logic is nicely bundled in the model
- Changes in rendering doesn’t impact the core game
- Supports different input controllers and/or bots
________________ | | | Controller |<============ User Input |________________| | | O | | /|\ | | / \ ______V__ __V______ | | | | | Model |<-----| View |=======> Screen Output |_________| |_________|