See Also:
Making a Game Engine: Transform Hierarchy
Before I get started I want to say a few things. Typically, you don't want to make a game engine, you want to make a game. I hope that this article can help you work on some core design concepts that you can apply when making your game. If you use good architecture you will find your game is less buggy and much easier to maintain. You will also find that you will be able to reuse a lot of code between projects. This reusable core becomes your game engine. Now before I talk about some game-engine-specific design I want to discuss some good design principles for programming in general.
General Programming Concepts
While the field of software architecture is vast and volumes of books could be written on the subject, I have chosen to focus on a few important principles that I find to be very important to making a solid game engine. This article alone does not intend to be enough to make a solid core for you game. That is a vast topic and would make a very large article if put in one place but I do hope to help you get one step closer to your goal of completing a game.
Cohesion
A highly cohesive class is one that has one and only one clear purpose. I think the best way to understand this is to give a bad example of cohesion then suggest ways to fix it.
class Game
{
Player player;
Enemy[] enemies;
void update()
{
foreach (Enemy enemy in enemies)
{
if (Math.abs(enemy.y - player.y) < EnemySightRange && Math.abs(enemy.x - player.x))
{
enemy.moveTowards(player.x, player.y);
}
}
if (input.keyDown('left'))
{
player.move(-1, 0);
}
// ... rest of player logic
}
void draw()
{
player.draw();
foreach (Enemy enemy in enemies)
{
enemy.draw();
}
}
}
So let me point out a few things wrong with this design. First of all, the class
Game has knowledge that there is a single player and a list of enemies. Why is this a problem? Because this class is not reusable nor is it very flexible. Let's say we were using this code and decided we wanted to make the game multiplayer. With the current code, we have single player hard-coded into the game class so adding another would likely be a difficult and buggy procces. Also, the enemy game logic is inside the game class as well. It doesn't belong there.
Another subtle problem with this design is that both the player and enemy directly have an
x and
y variable. This x, y position should be grouped together and the player's position should be accessed using
player.position or even
player.transform.position where the transform of the player contains information about location, rotation, and size of the player.
The last thing I want to point out is that the game class directly draws the players and enemies and that the player and enemies each have draw methods as well. The problem here is the way the player or enemy is drawn is linked directly with all other attributes of the player. This may not seem like such a problem on the surface but can create problems as things get more complex. It is better to remove drawing code from the player class and putting it into a seperate class.
So when writing your games how can you avoid low cohesion and classes and know when you need to break things up? The best way is to describe what that class does in words. If you cannot do it in one sentence without using the word 'and' then you probably should break up the functionality of the class. In the example above the responsibilities of the game class are updating the enemies
and updating the player
and drawing the player
and enemies. So how do you fix this? First of all, create a generic game object and add components to it that each have a specific task. One component to render it, another to handle game logic, another for physics, (etc...). This is far better than making sublcasses of a game object class to add custom behaviour. More on this later.
Coupling
Coupling refers to classes using other classes. An example would be a player class containing a weapon class. In this case the player is coupled to the weapon. To promote good design you want to have classes as decoupled as possible. You cannot, of course, remove all coupling between classes, because at some point one class will have to you use another class, but you want have as few dependencies as possible. So again I am going to give a poor example and show why it is less than ideal.
class PlayerRenderer
{
Player player;
bool isFlashing;
void draw()
{
if (Player.collider.isTouchingEnemy())
{
this.flash();
}
// draw the player
}
}
So this class also has some cohesion problems: the renderer is responsible for colliding against enemies and flashing the player, but I want to focus on the coupling problems. First of all, There is a player renderer. The name of the class
PlayerRenderer implies that this class depends on the player but it is also a render system. This class also has a reference to the player collider in it. This means that the drawing system is no longer independent from the collision system and the game logic. Now, changes in the collision system or on the player can effect the render code. Another issue is that the collider class has a
isTouchingEnemy method. This means that the collider knows about the enemies in the game. When a program is tightly coupled like this bugs in one part of the system can propagate and cause problems in seemingly unrelated places. By removing theses inter-dependencies you make it much easier to make changes in one part of the code without worrying about the rest. You could change entirely the rendering engine without touching the collider code. Why would this be useful? Well, if you wanted to make a game engine to support both DirectX and OpenGL all that needs to change is the render module. The same thing applies with porting games to consoles. All of the game logic can stay the same, all that needs to change is the rendering system, input sytem, and any other hardware-related systems.
How do you fix the problem in the code above? By making a generic renderer and a generic collider. These classes should have no knowledge about what type of object is using them. You should just be able to give them parameters, such as what image to draw and other parameters such as color and position and the sub system handles the rest. You then write a class that can interact with these different subsystems and orchestrate them together into a single working game. More on the subject later.
The Singleton
Singletons are evil. Don't use them.
To elaborate on this more, the use of singletons often creates problems for maintaining the code. They create implicit dependencies in the code and often lead to poor coupling. Since the singleton is accessible anywhere in the code, many times it is used everywhere in the code. If at any time you decide you don't want to have only a single instance of a class you are out of luck. The singleton is already all over the code and it will be a difficult process removing it. Another reason, and possibly one of the the biggest for not using singletons, is that they are often used because they are quick and easy and don't require you to think about the design. Meaning you don't have to carefully plan out your code. You simply make all of the major systems a singleton and allow any object to access anything else at any point. This usually ends up putting code where it doesn't belong and makes certain parts of the code do things it shouldn't be responsible for. When I stopped using singletons I found that I was able to come up with much better design decisions in my code. Because I thought more about how variables and objects would be passed around, it forced me to assess my design and think about it more. Do yourself a favor and don't use singletons.
Game Engine Design
So now that we have discussed some general design concepts lets focus on an example of how to structure the core of your game. Keep in mind that the method I am going to describe is not the only way to write a game engine. There is no one right way to make a game.
Game Loop
Before I get into game objects and scene I wanted to mention the game loop. Simply put, a game loop first updates the game, then draws a frame and continuously repeats those two things. The game loop should regulate the frame rate to match the refresh rate of the monitor and also compensate with larger time steps when the frame rate begins to drop. I am not going to spend any more time on this subject but
here is an excellent article on the game loops.
Scene
The scene is what manages all of the game objects. It should receive update and draw messages and pass those onto the components attached to game objects in the scene. A scene represents a level in a game. You can add terrain, enemies, objects, lights, cameras and anything else that belongs in games into the scene. The structure of a scene may look something like this.
class Scene
{
GameObjects[] gameObjects;
EventDispatcher eventDispatcher;
SceneRenderer sceneRenderer;
void update(float timestep, InputState input)
{
eventDispatcher.sendEvent("update", timestep, input);
}
void render()
{
eventDispatcher.sentEvent("render", sceneRenderer);
}
void createGameObject()
{
GameObject result = new GameObject();
gameObjects.add(result);
result.addedToScene(this);
// add any component in the gameObject to the eventDispatcher
// so it can receive update and render messages
// if the component doesn't have either method it isn't added
}
}
Now the scene is completely decoupled from the specifics of what kind of game this is. This core scene class can be reused in any number of game types and serves as part of the foundation of the game engine. A scene can also contain addons, such as the scene renderer or physics engine.
Game Object
A game object simply combines a bunch of components. The most common components are going to be the object's transformation, the renderer, physics, and game logic. The game object can also hold a reference back to the scene so the game object can query the scene for information, such as determining if the player is visible to an enemy by accessing the physics engine component of the scene and doing a raycast. The components on a game object should be able to access the other components on the object.
class GameObject
{
String name;
Component[] components;
Scene scene = null;
GameObject()
{
// require a game object to have a transform
this.addComponent(new Transform());
}
void addedToScene(Scene newScene)
{
scene = newScene;
}
void addComponent(component)
{
components.add(component);
component.addedToGameObject(this);
}
}
A transformation holds information about the game object's position, orientation, and size. Other game objects, such the renderer, physics, and game logic will read and write the values stored in the transform.
class Transform extends Component
{
Vector2 position;
float rotation;
float size;
}
The renderer can be attached to a game object to cause a shape or mesh to be drawn at the location of the transform. A game object's renderer will interact with the scene's renderer component to draw content to the screen.
class Renderer extends Component
{
Mesh mesh;
Materials[] materials;
void render(sceneRenderer)
{
sceneRenderer.drawMesh(mesh, materials);
}
}
To add game logic, such as moving the player, you simply create a component to control behavior.
// class to serve as a base class to all behavoir scripts
class Behavior extends Component
{
}
class PlayerBehavior extends Behavior
{
void update(float deltaTime, InputState input)
{
if (input.keyDown("left"))
{
this.gameObject.tranform.move(-1.0, 0.0);
}
// rest of player logic
// ...
}
}
class EnemyBehavior extends Behavior
{
GameObject player;
void init()
{
// searches the scene for an object named player
player = this.scene.findObject("Player");
}
void update(float deltaTime, InputState input)
{
if (Vector2.distance(this.gameObject.transform.position, player.transform.position) < EnemySightRange)
{
// move towards player
}
// rest of player logic
// ...
}
}
Now all of the player and enemy logic is contained in their own classes and these parts and components can be easily switched out, modified, and extended.
Conclusion
Writing games is not an easy thing to do, but if you are up to the challenge it can be a very rewarding experience. This article does not tell you everything on how to make a game engine. Most of the classes shown as examples are far from complete. There is still a lot you will need to learn about, such as how to make the different parts of the game engine work together, but hopefully this article has helped give a better picture on how to get started on making a highly resuable core to use on your game projects.
Article Update Log
Jan 11 2014: First published article
Jan 29 2014: Fixed typo with Behavoir class, clarified the intent of the article
Thank you for this article, it has some very good tips on building game engines. I will add this to my notes while i am creating my own game engine.