In a broad sense, a game object is anything in the game world that needs to be updated, drawn, or both updated and drawn on every frame. Even though it’s described as a “game object,” this does not necessarily mean that it must be represented by a traditional object in the object-oriented sense. Some games employ traditional objects, but many employ composition or other, more complex methods. Regardless of the implementation, the game needs some way to track these objects and then incorporate them into the game loop. Before we worry about incorporating the objects into the loop, let’s first take a look at the three categories of game objects a bit more closely.
Types of Game Objects
Of the three primary types of game objects, those that are both updated and drawn are the most apparent. Any character, creature, or otherwise movable object needs to be updated during the “update game world” phase of the game loop and needs to be drawn during the “generate outputs” phase. In Super Mario Bros., Mario, any enemies, and all of the dynamic blocks would be this type of game object.
Objects that are only drawn but not updated are sometimes called static objects. These objects are those that are definitely visible to the player but never need to be updated. An example of this type of object would be a building in the background of a level. A building isn’t going to get up and move or attack the player, but it certainly needs to be drawn.
The third type of game object, those that are updated but not drawn, is less apparent. One example is the camera. You technically can’t see the camera (you can see from the camera), but many games feature moving cameras. Another example is what’s known as a trigger. Many games are designed so that when the player moves to a certain location, something happens. For example, a horror game might want to have zombies appear when the player approaches a door. The trigger is what detects that the player is in position and triggers the appropriate action. So a trigger is an invisible box that must be updated to check for the player. It shouldn’t be drawn (unless in debug mode) because it suspends disbelief for the gamer.
Game Objects in the Game Loop
To use game objects in the game loop, we first need to determine how to represent them. As mentioned, there are several ways to do this. One such approach, which uses the OOP concept of interfaces, is outlined in this section. Recall that an interface is much like a contract; if a class implements a particular interface, it is promising to implement all of the functions outlined in the interface.
First, we need to have a base game object class that all the three types of game objects can inherit from:
// Member data/functions omitted
Any functionality that all game objects should share, regardless of type, could be placed in this base class. Then we could declare two interfaces, one for drawable objects and one for updatable objects:
Once we have these two interfaces, we can then declare our three types of game objects relative to both the base class and said interfaces:
// Update-only Game Object
// Overload Update function...
// Draw-only Game Object
// Overload Draw function...
// Update and Draw Game Object
// Inherit overloaded Update, overload Draw function...
If this were implemented in a language that provides multiple inheritance, such as C++, it might be tempting to have DUGameObject just directly inherit from UGameObject and DGameObject. But this will make your code very complicated, because DUGameObject will inherit from two different parents (UGameObject and DGameObject) that in turn both inherit from the same grandparent (GameObject). This issue is known as the diamond problem, and although there are solutions to this problem, it’s typically best to avoid the situation unless there’s a very good reason for it.
Once these three types of classes are implemented, it’s easy to incorporate them into the game loop. There could be a GameWorld class that has separate lists for all the updateable and drawable game objects in the world:
When a game object is created, it must be added to the appropriate object list(s). Conversely, when an object is removed from the world, it must be removed from the list(s). Once we have storage for all our game objects, we can flesh out the “update game world” part of our loop, as shown in Listing 1.4.
Listing 1.4 Final Game Loop
whilegame is running realDeltaTime = time since last frame gameDeltaTime = realDeltaTime * gameTimeFactor
// Process inputs...
// Update game world
Updateableo in GameWorld.updateableObjects o.Update(gameDeltaTime)
// Generate outputs
Drawableo in GameWorld.drawableObjects o.Draw()
// Frame limiting code...
This implementation is somewhat similar to what Microsoft uses in their XNA framework, though the version presented here has been distilled to its essential components.