Comments on Object Oriented Game Design
I was looking through gamedev.net posts and came across a guy who was looking for advice about how to architect his game to avoid using singletons. His solution was to pass around a lot of references to manager classes and he was looking for something better. I gave the following reply, which I thought was good enough to reproduce here:
My approach is to pass in external subsystems where they’re needed, but only as a last resort. In general, I think it’s good practice to try to minimize subsystem dependencies. If you’re having to pass a lot of subsystems around, it may mean that the way you’ve architected what data and functionality goes into each class isn’t optimal. Try to architect your classes so that they can do their job without having to know about external subsystems.
Let’s take a simple example of a keyboard input class that takes care of the low level stuff associated with reading key-press events (and possibly re-mapping them based on key assignments), and a player class that has a bunch of actions that can be performed. If implemented properly, the keyboard class doesn’t need to know anything about a player, and a player class doesn’t need to know anything about the entity. Instead, there needs to be a higher level object that knows about both the keyboard and player classes. I’ll just call this the “game” class for lack of a better term — but really it’s any appropriate higher level class. Anyway, the game class ties the keyboard input to player actions via some method. Since this is a simple example, I’ll say the game class will poll the keyboard input to see if the “jump” key is pressed, and if so, it calls the player::jump() function.
The advantage of this approach over singletons and globals is that you don’t need the player or keyboard to know about each other. This improves compile times. Much more than that, though, it makes your code very modular. If you want to add joystick support, you don’t have to change the keyboard or player classes at all. If you want to add network support, you don’t have to touch your keyboard or player classes at all. If you need to re-write the keyboard class, you don’t have to touch your player class at all. This example is pretty simple, and I might just be telling you things you
already know. But it extends to more complicated cases without trouble. For example, if your project is already very complicated, you probably don’t want to add functions to your highest level game class for connecting the keyboard class to the player class. Okay, so create a mid-level class called player_control or something. At the game class level just pass in the keyboard class and player class to the player_control class. The player_control class would then, in this hypothetical example, take care of checking the keyboard class for updates and calling player functions. You may ask “well, why do this if I just end up having to pass references to the player and keyboard classes around anyway?” The answer is that at least this way you pass player and keyboard references to a class whose job it is to know about players and keyboards, and you keep the modularity and independence of the lower level subsystems intact.
Anyway, I don’t think my advice was applicable enough and so I got rated negatively because of it (doh)… but I still think it’s good advice. If you’re trying to think of a way to allow access to your manager classes to classes that shouldn’t need to know about them, then something is wrong. I’ve learned it the hard way.