Chapter 3. Forge of the Gods – Shaping Our World
In the previous chapter, we have covered loading, managing, and releasing of external resources. To sum up, we have investigated mechanisms to ensure that textures, fonts, or sounds are ready to be used as soon as we need them. This chapter attempts to bring knowledge around a few key topics:
Entity systems in concept and practice
The viewable area of our world and scrolling
Tree-based scene graphs, rendering and updating of many entities
Composition of all elements to shape the world
When writing a game, we invariably find the need to conceptualize our vision of the game into actual data structures. In other words, it is extremely important to have a clear idea of the scope of our vision. The world in our mind doesn't even have to scale remotely to what we consider our real world; it is a product of our own creation. It is well within our power, as game developers, to forge the whole world as a simple board game table, or an ant farm, or even...
An entity denotes a game element in the world. In our game, possible entities are friendly and enemy airplanes, bullets, missiles, or pickups that increase the player's strength. Entities interact with each other: enemy airplanes can fire missiles, the player's airplane may evade them, and the missiles may explode if they hit the player's plane, dealing damage to it. The player's aircraft can touch a pickup to collect it, as a result of which it gets a new ability. The possibilities are nearly unlimited, and they may occur between almost any pair of entity types.
In our code, we represent entities using the traditional approach of an entity hierarchy. We have a base class called Entity
, which contains the data and functionality that all different kinds of entities have in common. We have multiple classes that derive from Entity
, and that implement specific functionality. These derived classes could represent airplanes, projectiles (such as missiles), or pickups. One commonality between...
At one point, we have to reflect about how the game is rendered on the screen. How do we draw all the entities, the scenery, and interface elements (such as a health bar)? A simple option is to have different sequential containers through which we iterate. For each element, we call a possible
Entity::draw()
function to draw the corresponding entity on the screen. We only have to make sure that objects that appear behind others (such as the scenery background) are drawn first.
The sequential rendering approach works well for many cases, but makes it difficult to handle an entity relative to another one. Imagine we have a formation of airplanes, where one is the leader and the rest follows it. It would be nice if we could set the position of the following airplanes dependent on the leader, in the sense of "plane A is located 300 units behind (below on the screen) and 100 units right of the leader" instead of "plane A is located at position (x, y) in...
In each frame, we update our world with all the entities inside. During an update, the whole game logic is computed: entities move and interact with each other, collisions are checked, and missiles are launched. Updating changes the state of our world and makes it progress over time, while rendering can be imagined as a snapshot of a part of the world at a given time point.
We can reuse our scene graph to reach all entities with our world updates. To achieve this, we implement a public update()
member function in the SceneNode
class. Analogous to the way we have proceeded for the
draw()
function, we split up update()
into two parts: an update for the current node, and one for the child nodes. We thus write two private methods updateCurrent()
and updateChildren()
, of which the former is virtual.
All update functions take the frame time dt
as a parameter of type sf::Time
(the SFML class for time spans). This is the frame time we computed for the Game
class in Chapter 1, Making...
A view is a concept that allows us to select the part of our world we want to see. You can imagine a view to work like an image recording device. Essentially, every game programmer needs to understand views in depth, simply because every graphical game will require that knowledge to be applied directly. Sometimes it is called view, camera, or differently. Be it a two-dimensional or three-dimensional simulation, this concept will always be present.
Talking about graphics in two dimensions, we can describe a view as a rectangle that represents the subset of our world that we want to see at a particular time.
Such a simple and yet powerful concept gives us the possibility to perform many interesting tasks, such as an animated camera following the player around, a spy camera that allows us to take a peek at a remote location, a combination of two views to create a split-screen multiplayer experience, as well as many other applications.
It is not hard to grasp the power of the view; you...
As you can observe in the C++ sample for this chapter, our aircraft travels continuously over a desert. This continuity can be achieved in many ways and with many different levels of detail and complexity. However, we chose to go in a very simple and yet effective way of doing it, using a feature that SFML provides out of the box.
In order to display our background sprite through the scene graph, we created a new SceneNode
type, the SpriteNode
, which acts as a simple sf::Sprite
that can be plugged into our tree structure. Conveniently, this is all we need to make our landscape. We only have to create SpriteNode
and attach it to our background layer of the scene graph.
To demonstrate the implementation of a new node type, there follows a small snippet of the SpriteNode
declaration:
Up to now, we have taken a look at entities and the scene graph, we know how to render and update objects in the world, and we have seen how views and scrolling work. We have a concrete knowledge about many building blocks, now it is time to assemble them to shape a model of our fictional world.
Completely unforeseen, we create a new class called World
. On one side, our World
class must contain all the data related to rendering:
A reference to the render window
The world's current view
A texture holder with all the textures needed inside the world
The scene graph
Some pointers to access the scene graph's layer nodes
On the other hand, we store some logical data:
The bounding rectangle of the world, storing its dimensions
The position where the player's plane appears in the beginning
The speed with which the world is scrolled
A pointer to the player's aircraft
Concerning functionality, we implement public functions to update and draw the world. We also add two private functions to...
Integrating the Game class
By now we already know how the
Game
class works, what a game loop is for, and how to take advantage of it. For this chapter, we take the previously used Game
class, and plug into it our newcomer World
class.
Because we obeyed a few principles, this integration is very easy and it's just a matter of having a World
object inside the
Game
class, and then letting it update and draw itself in the appropriate times.
Our application's
main()
function has a simple job. It allocates a Game
object, and lets it run itself through the run()
method. When the run()
method exits, the program releases its resources and closes.
Therefore, it is within the run()
method that the magic happens! It is responsible for managing the famous game loop, fetching input from the window system, updating the world, and ordering the rendering of the game.
In the next chapter, events and input will be covered in depth. For now, it is only important to understand how the drawing...
This chapter has moved away from the typical minimal examples, and has given you deeper insights to a possible game architecture. As always, keep in mind that we have chosen one of many design options—not every game needs a scene graph, sometimes a list of entities is enough.
In the next chapter you can expect information about handling both input events and real-time state of the keyboard and mouse. Also, you will learn about command systems to deliver input to game entities as well as dynamic key binding mechanisms, which allow you to configure the set of controls of a game in runtime.