This is the third post of the Game Programming Patterns book summary series. The first and second posts are about some selected patterns from the Gang of Four book, while this post covers the Sequencing Patterns.
Book Summary: Game Programming Patterns
- Patterns Revisited, Part 1
- Patterns Revisited, Part 2
- Sequencing Patterns
- Behavioural Patterns
- Decoupling Patterns
- Optimisation Patterns
The following are the patterns handling the passing of time in the game world.
Double Buffer is useful when we need to read the states of multiple objects in atomic time while the states can only be written slowly and/or sequentially. To do that, we prepare two buffers; one to be read and one to be written. We flip the buffers when the written buffer is ready to be read.
Double Buffer is common for graphics data. The buffer is rasterised sequentially and it takes a longer time compared to the time needed for the GPU to read and render it to the screen. Without a double buffer, the read time can overlap with the write time, causing a tearing effect. The old frame is displayed partially with the new one.
Keep in mind:
- The swap itself is not instantaneous.
- Two buffers mean double the memory allocation.
- How to swap the buffers?
- Swap pointers. It’s fast, but we can’t have persistent reference to the current buffers. The reader and the writer need to resolve the buffers’ address dynamically. The data in the flipped buffer is two frames old so it is probably can’t be reused.
- Copy the data between buffers. It’s probably slow because the output data basically need to be written into both buffers. But the data in the written buffer is exactly the previous frame data hence can be reused if we need them. The writer and the reader also have persistent reference to the buffers.
- How about the buffers’ granularity?
- Sequential data in memory. Monolithic with a single reference. If we have multiple objects writing the buffer, the design is probably not so pretty because the objects need to know the buffer’s reference and an index. But swapping buffers will be easy and fast.
- Multiple “buffer parts”. The multiple objects which read or write data have their own buffers. Swapping the buffers are probably slow because we need to iterate a list of these objects and tell them to swap their buffers.
A Game Loop loops during gameplay. This pattern is almost always used in game architectures. Even without user input, the game’s animations, music, and physics calculations need to keep running in the background. Each loop, it:
- Processes input
- Updates game states
- Tracks the actual delta time. This information can be used to control loop rate–frames per second, physics update speed, animation update speed, etc.
Keep in mind:
- The loop is always running, controlling the responsiveness of animations and input. Be efficient.
- Be mindful of the base OS’s/platform’s existing event loop.
The book explains the approach of implementing a game loop from the simplest way of just handling input-update-render without tracking delta time; to the good practice, which is keeping the update loops separate from the main loop so we can have multiple update loops running with different frequencies.
- Who owns the game loop?
- The platform/OS. For example, our target platform has an event loop. The core loop is already there so we don’t have to reimplement the loop (yay). We can be sure it’s optimised because it’s native. But because it’s there, we lose control of the timings for inputs and events.
- The Game Engine. No need to work with the platform-specific event loop. It’s nice if the engine gives us some control to optimise the game loop.
- No loops anywhere? What platform are we even working on? LOL. Congratulations, we have all the control to the game’s loop. Also all the work. Don’t block other important processes running in the platform by hogging all the resources, though.
- How about power consumption?
- As-high-as-possible FPS. It’s nice for PC, but we don’t want to inflict low-temperature burns on the hands of our smartphone users.
- Add an option for a lower, fixed FPS.
The Update Method is another pattern that is hardly ever absent in game architectures. The game world has objects. Each object implements an Update Method that simulates
n frame(s) of the object’s behaviour. Each
n frame(s), the Update Methods of those objects are called. We use this pattern if we need a simultaneous update of objects independently, over time.
Keep in mind:
- Use the State pattern if we need to pause and resume update.
- “One frame” update of objects aren’t truly concurrent. They can be called at random. Use Double Buffer to make sure states are not being read and written at the wrong time.
- Remember to be careful when creating/deleting objects with the Update Method. To avoid calling update on deleted objects, defer deletion and just mark those as do-not-update first.
What to do in the implementations:
- Favour composition over inheritance. We can use the Component pattern, and each Component object has its own Update Method.
- Add one base “Behaviour” class that defines the Update Method and the game loop. Have Component classes inherit it.
- For special update loop calculations, we can pass delta time to the Update Method.
- Who owns the Update Method?
- Entity class, or the GameObject class.
- Component class.
- A delegate class. Use the Type Object to turn the class into an “updatable type” class.
- How are the dormant objects handled?
- Use a single collection of both active and inactive objects. Use enable and disable flag. It’s a simple implementation but potentially wastes CPU time because dormant objects are never ignored.
- Separate the collections for the active and inactive object. We save CPU time because we ignore the inactive objects collection entirely. But we need to move around the object references from the active/inactive collections and ensure objects are added/deleted gracefully from both collections.
As mentioned, the Game Loop + Update Method + Component are essential for game architectures. Unity has MonoBehaviour, XNA has GameComponent.
That’s all for the Sequencing Patterns section. Next up is Behavioural Patterns–that is summarised here.