0 ratings 0% found this document useful (0 votes) 226 views 618 pages Game Programming Gems 3
The document outlines a comprehensive guide on game programming, covering various topics such as scheduling game events, mathematics, artificial intelligence, graphics, networking, and audio. It emphasizes the importance of a scheduler in managing real-time events and simulations within games, detailing its components like task and event managers. Additionally, it discusses the differences between real-time and virtual time, providing insights into task execution and event processing for efficient game development.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content,
claim it here .
Available Formats
Download as PDF or read online on Scribd
Carousel Previous Carousel Next
Save Game Programming Gems 3 For Later SECTION 1 GENERAL PROGRAMMING ......
1
13
14
15
1.6
47
18
Contents
Foreword...
Preface.
Acknowledgments.
‘About the Cover image
Contributor Bios
Introduction ...
Kim Palliser
‘Scheduling Game Events ..
Michael Harvey and Carl Marshall
‘An Object-Composition Game Framework . .
Scott Patterson
Finding Redeeming Value in C-Style Macros . .
Steve Rabin
Platform-independent, Function-Binding Code Generator ..
Alllen Pouratian
Handie-Based Smart Pointers .
Brian Hawkins
Custom STL Allocators ...
Pete Isensee
Save Me Now! . .
Martin Brownlow
Autolists Design Pattern
Ben Board19
4.40
1a
442
143
1.44
1.15
1.16
SECTION 2 MATHEMATICS ..
24
23
24
25
Floating-Point Exception Handling .......
Soren Hannibal
Programming a Game Design-Compliant Engine Using UML ...
Thomas Demachy
Using Lex and Yacc To Parse Custom Data Files ....
Paul Kelly
Developing Games for a World Market ..
Aaron Nicholls
Real-Time Input and UI in 3D Games ..
Greg Seegert
Natural Selection: The Evolution of Pie Menus ..
Don Hopkins
Lightweight, Policy-Based Logging . .
Brian Hawkins
Journaling Services .......
Eric Robert
Real-Time Hierarchical Profiling ......
Greg Hjelstrom and Byon Garrabrant
seneecoees 183
Introduction +165
John Byrd
Fast Base-2 Functions for Logarithms and
Random Number Generation ..
James McNeill
Using Vector Fractions for Exact Geometry ..
Thomas Young
More Approximations to Trigonometric Functions
Robin Green
Quaternion Compression ........
Mark Zarb-Adami
+170
Constrained Inverse Kinematics ..
Jason Weber27
SECTIONS ARTIFICIAL INTELLIGENCE ...
34
3.2
34
3.5
3.6
3.7
SECTION 4 GRAPHICS ...
44
42
Cellular Automata for Physical Modeling ...
Tom Forsyth
Coping with Friction in Dynamic Simulations . .
Miguel Gomez
Introduction .
Steven Woodcock
Optimized Machine Leaming with GoCap ..
Thor Alexander
‘Area Navigation: Expanding the Path-Finding Paradigm ......... 240
Ben Board and Mike Ducker
Function Pointer-Based, Embedded Finite-State Machines ....... 256
Charles Farris
Terrain Analysis in an RTS—The Hidden Giant .
Daniel Higgins
‘An Extensible Trigger System for Al Agents,
Objects, and Quests
Steve Rabin
Tactical Path-Finding with A* ...
William van der Sterren
A Fast Approach to Navigation Meshes .
Stephen White and Christopher Christensen
Choosing a Relationship Between Path-Finding and Collision ..
Thomas Young
268
Introduction ...
Jeff Lander
‘T-Junction Elimination and Retriangulation ......
Eric Lengyel
Fast Heightfield Normal Calculation . .
Jason Shankelvill
43
44
45
46
47
48
40
4.10
41
4.12
4.13
414
415
4.16
47
4.18
4.19
Fast Patch Normals...
Martin Brownlow
Fast and Simple Occlusion Culling
Wagner T: Corvéa, James T: Klosowski, and Claudio T. Silva
Triangle Strip Creation, Optimizations, and Rendering ..
Carl S. Marshall
Computing Optimized Shadow Volumes for Complex Data Sets .. . 367
Alex: Vlachos and Drew Card
‘Subdivision Surfaces for Character Animation .. . +872
William Leeson
Improved Deformation of Bones ........+.+0+5 +384
Jason Weber
A Framework for Realistic Character Locomotion ... +1394
Thomas Young
A Programmable Vertex Shader Compiler ... +404
‘Adam Lake
Billboard Beams
Brian Hawkins
‘3D Tricks for Isometric Engines ........+
Greg Snook
Curvature Simulation Using Normal Maps .......++
Oscar Blasco
Methods for Dynamic, Photorealistic Terrain Lighting
Naty Hoffman and Kenny Mitchell
Cube Map Lighting Techniques ...
Kenneth L, Hurley
Procedural Texturing ...
Mike Milliger
Unique Textures ..
Tom Forsyth
‘Textures as Lookup Tables for Por-Plxel Lighting Computations .. . 467
Alex Vlachos, Jobn Isidoro, and Chris Oat
Rendering with Handcrafted Shading Models...
Jan Kautz
413
477SECTIONS NETWORK AND MULTIPLAYER ..
64
6.3
5.4
5.5
8.7
SECTION 6 AUDIO ..
6.4
6.2
6.3
Introduction . .
Andrew Kirmse
Minimizing Latency in Real-Time Strategy Games
Jim Greer and Zachary Booth Simpson
Real-Time Strategy Network Protocol...
Jan Svarovsky
A Flexible Simulation Architecture for
Massively Multiplayer Games . .
Thor Alexander
Scaling Multiplayer Servers .
Justin Randall
‘Template-Based Object Serialization ..
Jason Beardsley
Secure Sockets ........
Pete Kensee
‘A Network Monitoring and Simulation Too! ..
Andrew Kirmse
Creating Multiplayer Games with DirectPlay
Gabriel Robweder
Wireless Gaming Using the Java Micro Edition .......
David Fox
561
Introduction ..
Scott Patterson
‘Audio Compression with Ogg Vorbis...
Jack Moffiee
Creating a Compelling 3D Audio Environment ..
Garin Hiebert
Obstruction Using Axis-Aligned Bounding Boxes ...
Carlo Vogelsang
5876.4
6.7
Using the Biquad Resonant Filter .....
Phil Burk
Linear Predictive Coding for Voice Compression and Effects .
Eddie Edwards
‘The Stochastic Synthesis of Complex Sounds ...
Phil Burk
Real-Time Modular Audio Processing for Games ...
Frank Luchs
Appendix: About the CD-ROM . .>
Scheduling Game Events
Michael Harvey and Carl S. Marshall,
Intel Labs
michael.harvey@intel.com,
cari.s.marshall@intel.com
‘anaging events in a game—animation updates, object collisions, and so forth—
can be a daunting cask if chere is no clear understanding of how the events are
organized and executed. This gem will explain how a scheduler can provide both
‘organization and flexibility to your game framework.
‘We will begin by describing what a scheduler is and why itis useful, and end with
advanced topics on scheduler development. A simple scheduler is provided as source
code on the CD-ROM.
‘With the growing sophistication of computer games, real-time events and simu-
lations are virtually a standard in today’s game architectures. What is needed is a way
to manage and execute multiple events per frame, or many times within a frames
mestep. A scheduler can manage game events in a very flexible fashion, as well as
te a modular approach for extensibility.
‘A few examples of game technologies that can effectively utilize a scheduler are
physics simulations, character animation, collision detection, game Al, and render-
ing. A key aspect in all of these technologies is time. Many of these simulation tech-
nologies can become enormously complex, with hundreds of independent objects and
processes being updated at various time intervals. For instance, a physics simulation
will break down time into small, discrete intervals for each object in order to update
the object’ motion [Bourg01]. By providing a finer resolution of ime, the simulation
will have a much higher degree of accuracy. In this case, many objects and time inter-
vals are managed by the same scheduling code, so efficiency isa vital concern in pre-
venting scheduling bottlenecks.
Another important aspect of the scheduler is its ability to add and remove objects
‘on the fly. This allows for new entities to come into a game and participate in the sim-
ulations along with the rest of the game's entities without missing a beat, and then be
removed from scheduling when they are no longer needed.Scheduler Concepts
‘The basic components of the scheduler are a task manager, an event manager, and a
clock (sce Figure 1.1.1). With these components, the scheduler ean generate time- oF
frame-based events and execute event handlers. Throughout this gem, we will refer to
event handlers as tasks.
on feinan f-efrrp-ef Tp
FIGURE 1.1.1 Basic scheduler architecture.
Task Manager
‘The task manager handles the registration and organization of tasks. Each task has a
standardized interface that contains a callback function for the manger to execute. The
task manager maintains a list of tasks, along with scheduling information about each
‘one—such as start time, execution frequency, duration, priority, and other required
properties. It might also contain a user-data pointer or performance statistics.
Event Manager
‘The event manager isthe heart of the scheduler. Each task in the task manager defines
cone or more events that it handles. An event is a moment in time when a task isto be
executed. For example, in Figure 1.1.2, Taskl defines events at times 10 and 15. The
event manager generates events as needed to trigger the execution of tasks.
Real-Time Versus Virtual Timo
A real-time scheduler is faily simple in concept—the event manager sits in a loop.
watches a real-time clock, and as soon as a target time is reached, it fires an event. In
a real-time system, latency is critical. If a task takes too long, then it might interfere
‘Task Manager
} Event Manager
Get Next Task = Taskt TTaskt
Je Task! noxt_time = 10 ext time = 10
Update Ciock period =5
[e clock time = task ime = 10 ————
Execute Task Presa i
Update Task next time 1 | next.time = 12
je new time = 15 perid = 20
FIGURE 1.1.2 Event processing.with the start of the next task. Since each task occurs exactly at its scheduled time, the
time between tasks is essentially wasted from the schedulers frame of reference.
From the task’s point of view, time is only a number. Times can be compared, and
lapsed time can be computed, based on this comparison number. The scheduler can
simulate a given time or the passage of time by manipulating this number, indepen-
dent of real-time—hours can pass in the blink of an eye, or time can be halted. This
is the basis of virtual time.
Virtual time is extremely useful because it allows the scheduler to execute tasks
when it is most convenient to do so, instead of when real-time dictates. It allows a
sequence of events to be run quickly forward, stopped, recorded, and replayed. Ie also
makes it possible to debug a ‘real-time’ application by stepping one interval ata time.
‘A viral time scheduler divides time into frames. Tasks are executed in batches
beeween frames, running in ‘virtual time,’ and then are synchronized with real-time
when each frame is rendered. If che frame rate is high enough, the illusion of real-time
is achieved. However, a few dozen milliseconds between frames isa lot of time for the
computer, especially if ic is managed efficiently. By batching all tasks together into a
single block, the remaining time can be used for something else (see Scalability).
Latency issues can be almost eliminated.
We'll refer to virtual time as simulation time in this gem, since all objects within
the simulation use it as a reference. If simulation time is stopped, then simulation
pauses as well. When it resumes, objects in the simulation do not detect any break in
continuity. Simulation time starts at zero at che beginning of the simulation.
‘Tasks are executed sequentially while simulation time is updated between tasks.
‘Asan example, assume each frame has a length of 20 ms (see Figure 1.1.3). IF we have
events that occur at 51 ms and 54 ms, then they will be processed during Frame 2.
The event manager does not know how long Frame 2 is until it has ended; so at the
beginning of Frame 3, it looks at real-time and sees 60 ms. It can now process the
tasks scheduled for Frame 2. Taskl is the first, at 51 ms; but the simulation clock is
still at the beginning of Frame 2. The clock is advanced to 51 ms and Taskl is exe-
Frame 2 - Real-Time Frame 3 - Real-Time
coms
End Frame Start Frame
coceesseen] Task [Task Renders...
4doms ‘sims 54ms 60ms
Frame 2 - Simulation Time Frame 3 - Simulation Time
FIGURE 1.1.3 The difference between real-time and simulation sime in Frame 2ccuted. Once Task is done, the clock is advanced to the next event at 54 ms and Task2
isexecuted. No more events are scheduled for this frame, so the clock is set to the end
of the frame (60 ms) and the frame is tendered. (If you are using an offscreen buffer,
the frame was probably already rendered, and this is merely copying the image to the
display.) Any unused time can be used for additional processing (see Scalability on
how to use those extra processing cycles).
In this model, task execution and frame rendering always lag slightly behind real-
time, But this is not perceptible to the viewer, and ic allows us to work with a variable
frame rate. If the frame rate slows down, the scheduler can compensate so that the
simulation appears to run at a constant rate. Ifthe frame rate is fixed, the scheduler
can predict the start and end times, and perform event processing in advance. How-
ever, if the machine becomes significantly overloaded, the scheduler cannot compen-
sate, and the game will become slower.
Event Types
Frame events are the simplest ypes of events and occur once per N frames, or every
frame (N= 1). They can also occur before or after the render event. Time events, on
the other hand, occur in simulation time and are not specifically synchronized with
frames. For example, a time event can occur every 10 ms, regardless of the rendering
fame rate. tis also possible to combine time events and frame events. For example,
an event could be scheduled to occur 10 ms after the start of every frame, oF it could
‘execute five times per frame, evenly distributed in simulation time.
Clock
‘The clock component of the scheduler keeps track of real-time, the current simulation
time, and the frame count. The accuracy of the clock will determine the accuracy of
the simulation—a 1-ns resolution clock will be much more accurate than a |-ms res
olution clock. For most purposes, 1-ms resolution is adequate. If greater resolution is
required, one could use the 1-ms hardware clock and subdivide the real-time ticks as
needed to increase the resolution. A floating-point clock could be used as wel.
although careful attention would have to be paid to dealing with rounding errors.
Sequencing
‘The event manager handles the sequencing and generation of events. Since tasks are
triggered by events, proper ordering occurs naturally. For example, le’s define two
tasks
TaskI: Run every 5 ms from 5 to 15, normal priority.
Task2: Run every 4 ms from 11 to 19, high priority (see Table 1.1.1).In some
cases, tasks might be set to execute at the same time. In the example,
both Taskl and Task2 execute at time 15. Since Task? has higher
priority than Taskl, itis executed frst. If priorities are equal, or no4.1. Scheduling @:
priority system is implemented, they are handled round-robin. Priority is also
useful for ordering frame-based tasks
‘With hundreds of potential tasks, the task manager must manage things intelligently.
A brute-force search for the next task is clearly not very efficient, While many meth-
Ganzi ods are possible, che example programs on the CD-ROM make use of an ordered list.
The tasks are stored in alist according to their next execution time—the head of the
lise is always the next task to be executed. The event manager only needs to look at the
first task to determine when the next event should occur. When an event occurs, the
foremost task is ‘popped’ off the list and executed, its next time is updated, and it is
re-inserted into the list according to its updated execution time.
Besides avoiding lengthy searches, this approach also has the advantage that fre-
«quent tasks stay close to the front of the list (almost like a cache). Infrequent tasks stay
out of the way and ‘bubble up’ automatically at the proper time.
Tis often the case that a registered task must be modified on the fly, which might
involve adjusting its priority, period, duration, or even deleting it before itis finished.
In order to update a task, there must be some external means to locate it. A unique
registration ID can be assigned to locate the task in the list.
A Simple Scheduler
‘Now that we have discussed the various concepts and components of a scheduler, we
- will demonstrate how a simple scheduler can be built and utilized. Code for the exam-
<> ples provided can be found on the CD-ROM, The provided sample scheduler
mme® (Scheduler, Clock, and ITask) can also be used as a library. Two sample clients are pro-
vided (sample.exe and win.exe).
The scheduler’s design hinges on ewo components—the scheduler engine itself and
the ITask plug-in interface (see Figure 1.1.4).Section 1_ General Programming
‘Scheduler Library ‘Application .
Task Manager an ‘SomeClass
| _____-p| Task intertace
Clock Taskt b+ eee
—P other Class
Event Manager
> Other Class
FIGURE 1.1.4 Relationship between scheduler components and client application.
In order for the scheduler to run, someone needs to call it. In a non-GUI pro-
gram, this is as simple as coding a loop and executing:
while (running)
Scheduler.ExecuteFrane() ;
There are ewo ways to integrate a scheduler into a message-pump GUI, such as
‘Windows. The first is by modifying the message loop to process messages and then
execute the scheduler. This is the most obvious approach, but it suffers from sched
uler ‘freezes’ while a window is being resized.
‘The second approach is to create a Windows timer, and use that to call the sched-
uler. Since timers are not interrupted by window dragging, the scheduler runs contin-
uuously in the background. A sample application (Win) has been provided on the
€=_> CD-ROM that shows how to use a scheduler in a Windows application. Let’s look at
this application more closely.
The core Windows code is quite simple. The scheduler is run off a ¥M_TTMER mes
sage and has two types of scheduled events. One updates the position of each ball
every 15 ms, while che render event writes all che balls to an offscreen buffer. Win-
dows can then painc the screen from this officreen buffer on an as-needed basis. The
simulation and rendering occur without any special Windows programming. This
example demonstrates the use of multiple simulation tasks and adding/removing casks
on the fly.
Often, games have frame rates that vary, depending on system capacity and lead,
yet the velocity of moving objects remains constant. While both sample applications
have a fixed frame rate, the provided scheduler does support this constant velocity
technique. The key isin the Clock. Update) method that samples the actual real-time
and advances the simulation by the elapsed time. [Fan object moves NV units in 60 ms
ic does not matter whether the system renders two 30-ms frames or three 20-me
frames—the object moves the same distance in the same real-time, so the velocity isScheduling Game Events 11
constant. If you wish to have the velocity of simulated objects increase or decrease
with the frame rate, change the Clock.Update() method so that it advances in fixed
intervals, instead of reading the real-time clock.
So, how does the scheduler manage time, anyway? We need to register some
events and see how it works.
‘Scheduling
‘The first step in scheduling a task is to specify it as a time event, a frame event, or a
render event. This code will schedule a frame event that starts on Frame 200, runs
every third frame, and ends before Frame 210 (start 200 + duration 10):
scheduler. Schedule(TASK FRAME, 200, 3, 10,
pSomeHandier, puserPointer, 8d);
‘This task would run on Frames 200, 203, 206, and 209. After executing the final
iseration, the task expires and is deleted by the task manager. Tasks with duration 0
are perpetual and never expire. In certain cases, you might want to remove a task
before itis complete, or you might wane to manually end a perpetual task. To do this,
you Terminate() it using the task ID.
How does the frame get updated? Bach time the scheduler ExecuteFrane()
method is called, it first calls Clock, BoginFrane( ), which starts a new frame by updat-
ing the frame count and computing new frame start and end times. After updating
the frame count, it executes all time events, advances the simulation time to the end
of the frame, executes all frame events, and then finally executes the render task. (In
the sample scheduler, the render task is a special frame task that does not have a start,
period, or duration—It always executes once per frame.)
The entire simulation can be stopped or restarted using the Run() and. Stop()
methods. When the scheduler is stopped and then restarted, it computes the elapsed
time and subtracts it from the total simulation time. While stopped, the scheduler
still performs renders and frame events, but time events are suspended.
Advanced Concepts
There are a number of ways in which the scheduler can be improved or better utilized.
Scalability, simulation and multithreading are a few of these methods.
Scalabiiity
A common problem in game development is scalability. The game should take advan-
tage of all the available processing power to provide a richer experience, but it still
nceds to function well on less-powerful systems. Computationally expensive features
need to be throttled down or turned off completely. The game should also ‘play well”
in a multitasking environment—a game that utilizes most of the CPU might prevent
J, if the OS
the OS from responding co user input in a timely manner. In addi412
Section 1_ General Programming
launches any housekeeping tasks in the background, it could slow the game down.
Ideally, the game should adjust dynamically o the conditions ofthe system.
Collecting the performance data is half the battle. Since the scheduler can
become a bottleneck by handling all processes, it is a perfect place to put performance
monitors.
‘As described earlier, the clock takes a snapshot of the current time and compares
it to the last frame to determine elapsed time, The scheduler can also determine the
clapsed time of each task by comparing che star time to the end time, This informa-
tion can either be communicated to the task, or it can be used to determine whether
‘or not to run the task and/or how often.
One way to provide scalability is to require time budgets—the more power, the
more time allowed. A task might have a time budget per frame. The scheduler tracks
the accumulated budget and the accumulated running time, and only executes the
task if the current budget exceeds the actual time. For example, a task might have a
time budget of 2 ms per frame, but it runs in 3 ms on a slow CPU. The scheduler will
skip every third frame in order to keep the task within its budget (see Table 1.1.2).
‘Table 1.1.2 Time Budget Per Task
Some tasks might have a time budget ‘threshold’—if they exceed their budget
instead of running part of the time, they do not run at all. The scheduler can alse
determine the time needed to perform the entire simulation by summing all of the
tasks. The difference berween processing time and frame length is idle time, Ideally, the
game should use all che available time to improve gameplay, but should not use mow
than what is available, or the frame rate will slow down. The scheduler can add tasks te
fill idle time or remove tasks to reduce the load. (There must, of course, be some was
to specify which tasks can safely be added or removed.) By supplying overall systere
usage statistics to tasks, the tasks can chen scale themselves to use mote or less time
based on the data received. It is probably best to have a 5% to 10% idle target to allow
for minor fluctuations in actual processing time without slowing things down.
Other options that provide scalability include increasing or decreasing of time
budgets, scheduling of idle rasks (which only run during idle time), garbage collectio:
or other housework, graphics enhancements, or improvement of the AI. When doing
this type of management, itis important to avoid oscillation between extremes. The
can be done by limiting adjustments to small incremental changes rather than large———
jumps, or by statistical analysis of the effect of previous adjustments in order to
improve prediction.
Simulation
The scheduler can be used to drive a simulation system. Most simulation engines
break time down into discrete steps for purposes of animation and collision detection.
The scheduler described in this gem is perfect for this type of simulation system.
Ina simple example ofa lunar lander, the lander has a vertical velocity and a for-
ward velocity. Each timestep adds gravity. If we use AI to contzol the vertical thruster
of the lander, then at each timestep, the AI samples velocity and adjusts chrust to
compensate, allowing for a controlled descent. The timesteps need to be small enough
to give the AI time to react—otherwise the lander will hie the ground before it can
respond effectively. For collision detection, again, you want small timesteps so that
the lander will intersect the surface rather than passing completely through it.
Multithreading
Ieis possible for the scheduler to manage the execution of subthreads [Carter01, Daw-
son01]. There are many reasons why you might wish to do this. For example, some
tasks might work better as a continuous process rather than a series of discrete events
{Otaegui01}. Such tasks can be written as a thread, and the scheduler can control how
much time the thread is allowed for processing. This approach allows true preemptive
multitasking while actually enforcing a time budget.
Multiprocessing systems are slowly becoming more common, and it is likely that
multiprocessing will become a standard feature in the near future. Games that are able
to take advantage of multiple processors will be able to outperform games written for a
single CPU. An easy way for a game to utilize multiple CPUs is to make ic multi
threaded and let the OS do the work of distributing the threads on available processors.
‘A.mult-CPU scheduler could activate several threads at once so that they could
run concurrently. It could also spawn event handlers into specific threads so that mul-
tiple events can be handled concurrently.
Conclusion
‘There are a variety of reasons to use a scheduler—portabiliy, flexibility, and support
of simulations. A quality scheduler needs to be flexible and efficient. This gem has
covered some of the basic scheduler concepts, has provided a sample scheduler, and
has shown how to integrate it into conventional and GUI-based applications. Help
organize your events by using a scheduler in your next game.
[Bourg01] Bourg, David M., Physics for Game Developers, O'Reilly, 2001
[Carter01] Carter, Simon, “Managing Al with Micro-Threads,” Game Programming
Gems 2, Charles River Media, Inc., 2001._——____—___ Secon 1_ General Pragramming
[Dawson01] Dawson, Bruce “Micro-Threads for Game Object Al,” Game Program-
ming Gems 2, Charles River Media, Inc., 2001.
[LlopisO1] Llopis, Noel, “Programming with Abstract Interfaces,” Game Program-
ming Gems 2, Charles River Media, Inc., 2001
{MirtichOO] Mirtich, Brian, “Timewarp Rigid Body Simulation,” Computer Graph-
ics Proceedings, SIGGRAPH 2000: pp. 193-200.
[Oraegui0l] Otaegui, Javier, “Linear Programming Model for Windows-Based
Games,” Game Programming Gems 2, Charles River Media, Inc., 2001.1.2
we
An Object-Composition
Game Framework
Scott Patterson, Next Generation
Entertainment
scottp@tonebyte.com
scott@gameframework.com
this gem will present a design for a game framework based on object composition
and explain its advantages and design philosophy. We will present reasons why
this kind of framework can be useful for implementing the work required for games.
This game framework can serve as a reference for your own game systems. You
can create new systems with the capabilities that you need and create new tasks that
perform the actions that you need.
‘When we talk about a programming framework, we are referring to a system of
objects that work together to provide certain services. An application framework is a
collection of classes that provide the services necessary for creating applications. Our
goal in this gem is to find out what kind of framework we can build to help create
game applications.
‘There are good reasons to use a framework to build an application. Getting some-
thing running quickly is a primary reason. Time is money, after all. Frameworks typ-
ically contain built-in features, consistent behavior and structure, and well-known
rules for object access, object ownership, and object lifetimes.
In this gem, we first summarize game development stages to get an overview of
the work that is required. We then discuss the game framework design issues. Finally,
we present an overview of the game framework implementation provided on the
CD-ROM.
Game Development Stages
The need for a game framework will vary as development progresses. Table 1.2.1
shows a listing of typical game development stages and identifies the typical goals at
each stage- — ei Sections General Prooeeng
‘Table 1.2.1 Typical Goals During Game Development
‘Stage Goal
Concept Design of functionality and aesthercs, Creation of character, story, and mission
ennui Vnen ne nnn erneracee eeeeenren nner rus ereer toes ere
Protorype Demonstration of key gameplay dements through proof-of-concept demos.
eee ee
Payable Demensteation of atleast one mision or level being played fom wart vofnish.
Production Completion of designs and implementation forall missions and levels
‘Wrapping Integration of various game modes and screens, Includes story segments, taining,
mmisson/level selection, winlos,statsslscores, sevelload, paute/restare, options!
configuraion. as
Teng Solving design and implementation problems. Solving compaaibiliy ones
Integration of alernace drivers,
Release Shipping the game on is frst plaform. Pang?
Conversion Alternate language versions. Alernate plavforns
During the early stages (concept, prototype, playable), the choice of framework
‘might be focused on getting the program running quickly. At this time, ics quite pos-
sible thac the framework does not seem as important as creating the demonstration of
concepts. However, if the framework is not also designed to be useful for the other
stages of game development, then you might find yourself losing time to refactoring.
During the production stage, tools such as viewers and editors become essential.
Viewers are necessary for developers to see how their content looks “in the game.”
Editors are required for developers to make adjustments to any aspect of the game.
While these kinds of tools can be separate from the game application, it is often
required for viewer and editor capabilites to be incorporated into the game. To do
this, there must be code for the “consumer” aspect as well as for the “developer”
aspect. The goal isto build a framework that leverages the shared components of these
features, yet allows them to be developed independently.
During the wrapping stage, we must integrate the various game modes and
screens into one seamless product. This integration sometimes suffers from schedul-
ing delays or restructuring of the original design. Having a framework to help manage
these game modes and screens, and even the transitions between them, will help this
process go more smoothly.
During the testing stage, we might need the ability to start the game in various
modes or at certain points within the game. We want our framework to provide this
kind of flexibility to make it easy to define these various modes and entry points. We
might also want to include the ability co switch between drivers while inside the
game. If our framework binds a particular type of video technology to our applica-
tion, switching video drivers would not be possible, Whether we provide driver-
switching or not, we can add logging capabilities to our framework to aid the process
of compatibility testing.1.2, An Object-Composition Game Framework
az
Afier we reach the release stage, the game team might go on to do conversions of
the game, or it might be shipped off to other developers to do the conversions. Either
way, if our framework is hard to port to another platform, it will cause delays. We
would rather have the conversion team spend their time putting in new features and
sts for each platform, rather than spend their time scruggling to get it
ign
Now that we have an idea of the kind of work required to make games, we can look at
the design issues in creating a framework. We will cover platform dependence, game
dependence, object composition, inheritance, frame-based code, function-based
code, operation order, object lifetimes, and task integration.
Platform-Independent Vorsus Platform-Dependont
Games are usually filed with many concepts that transcend operating systems and
platform technologies. These concepts determine the player’s enjoyment through
“gameplay” and “depth.” Conversely, games are also commonly written to take advan-
tage of specific hardware features that help identify the presentation quality. This pre-
sentation quality is often responsible for extending a game's feeling of “immersion.”
Frameworks need not be bound to operating systems and technologies. We can
define platform-independent system interfaces for our framework rather than plat-
form-dependent system interfaces. Even though these interfaces are platform-inde-
pendent, we can use a factory system to create the concrete implementations for
specific platforms.
‘The more we can separate the game's conceptual work from platform specifics,
the easier it will be to replace only the platform-specific code for conversions. So, part
of our goal in creating a game framework is making i easy to keep game concepts
independent of the operating system and platform technology details whenever possi-
ble or practical.
jopondont Versus Gamo-Dopondent
IF we want to use a framework for many games, it makes sense to have the framework
be game independent. However, if we are going to use a framework for a single game
‘on several platforms, it might be acceptable to have portions of the framework be
game dependent.
For example, if our game controls a specific type of character that has many
dynamic visual details that depend on the character's state, we might want the render-
ing code to access game-specific states and decide how the object should be rendered.
This kind of situation can reduce the number of system interface calls, which simpli-
fies and speeds up the code.Section 1_ General Programming
One way to make an application framework intuitive is to use the template method
design pattern, and create objects thar are a subclass of an application class. When we
do this, we treat application initialization and destruction as algorithms that sub-
classes can redefine. This kind of design pattern is called “class behavioral” because it
uses inheritance to distribute behavior between classes
Another way to define the steps of application initialization and destruction
(without inheritance) is to define these steps asa list of tasks to process. A task system
class can coordinate task execution, and a resource system can reference and manage
the task lists and task objects. Now we are “object behavioral” because we are using
object composition and our resource system as our mediator for these objects.
Using a task system like this means that we can now build a framework based on
object composition rather than inheritance. Our task system controls task objects
through their interfaces, and our tasks perform work by calling object interfaces. This
also means that our framework will not have the typical inverted control structure
that is a result of the template method. Instead, our task objects control the software.
Perhaps this is an object framework rather than a class framework, but it isa frame-
work nonetheless.
‘The Design Patterns book {GoF94] discusses many advantages of object composi-
tion, Ic also highlights two principles of object-oriented design:
‘+ Program to an interface, not an implementation.
‘+ Favor object composition over class inheritance.
sed Operation
There are many types of software that are not concerned with frame timing, where
functions may take seconds, minutes, or even longer to complete. This function-
based operation can be much easier to program than frame-based operation.
Most games must be visually and aurally responsive, with many animations and
deuals being calculated every frame. Each time a visual image and/or audio buffer is ren-
dered, we have created a frame, Games may be rendered at speeds up to 50 or 60 frames,
per second. This kind of frame-based operation requires game software to execute in
short spurts of time. Ifa lengthy operation (over 1/60th of a second) isto be performed,
it must be broken into shorter pieces or be performed as a background task.
For our framework, frame-based operation will require a frame system class that
can tell our task system class when to call frame-synchronized tasks. Our task system
will also be able to process tasks that are not frame-synchronized, which we will call
“asynchronous tasks.”
Since the frame system controls when to call frame-synchronized tasks, we can
also offer the ability to manually step through frames and choose particular frame
rates. This can be useful for checking animation playback details as well as for other
debugging and testing purposes.
Frame-Based Versus FunctionDynamic Versus Static Operation Order
‘There are many types of software operations that need to be done in a specific order.
For these operations, we must call functions in a particular order or submit tasks in a
specific order to our task system. This is an example of static operation order.
‘Games are normally filled with various screens and transitions that are not typi-
cally connected in a specific order. Instead, the player's actions determine what hap-
pens next. This is an example of dynamic operation order.
For our framework, we can provide dynamic operation order by enabling task
objects to access the task system interface and submit new tasks.
Dynamic Versus Static Object Lifetimes (and
‘Ownership Issues)
In hierarchical systems, we might find that base objects own certain objects that are
used by inherited objects, while inherited objects can create objects that the base
objects know nothing about. Often the lifetimes of such objects are built into the
hierarchy, and inherited objects cannot dynamically create and delete them. The life-
times of these objects are static in this sense.
In an object-composition framework, it could be confusing to know when a par-
ticular object owns another object. To resolve this, we can assign object ownership to
our resource system. This way, any task can connect with system resources as needed
and not be given management responsibilities. We give our resource system the power
of object ownership and our tasks the power of object access, which limits the confu-
sion over ownership issues.
‘We can make the lifetimes of these objects dynamic by having tasks issue com-
mands to our resource system, We can direct the resource system to load and dump
objects in collections. For example, a load collection command can be issued before
tasks that need the loaded objects are started. A dump collection command can be
issued after those tasks are finished. Alternatively, we can have objects around for the
entire lifetime of the application.
Horizontal Versus Vertical Integration of Tasks
In hierarchical systems, it might seem like we are always “under” other objects that
control us and that our relationship with those objects is “fixed.” Tasks feel vertically
arranged, and changes to higher parts of the system can have far-reaching effects on
the operation of lower parts of the system.
In object-composition framework, it might seem like our programming environ-
‘ment is “flat” and our relationships with certain objects is more “dynamic.” Tasks feel
horizontally arranged, and changes to certain tasks in the system will have little or no
effect on other tasks in the system.20
Game Framework Implementation
Now we present an overview of the implementation of our framework chat meets
these challenges. The framework is composed of systems and tasks. A special kind of
task called the “frame player” provides high-level control of audio-visual rendering
and logic.
‘Systems
The systens_t class contains pointers to the pure interfaces [Stroustrup97] of the sys-
tems that our game will usc. We choose to provide access to these systems through
pure interfaces so that dynamic system switching is possible and platform-dependent
system code is separated from the platform-independent code that accesses the sys
tems. The interfaces available in the Systems_t class are summarized in Table 1.2.2.
Table 1.2.2 The Interfaces Available in the Systems t Class
System Summary
Logsys_t Handles ll message logging from the game. Optional outpus types include
text boxes oF fies
Errorsys_t Handles ero information and sa
TineSys, ‘Repors timing informat __
FactorySys_t Creates objecs wing Factory IDs
Resourcesys t Manages object instances using Instance IDs, _
TaskSys_t ‘Manages task execution and control seErEsrEnIaa
windowsys_t Provides window system management and contol
Framesys_t Provides frame synchronization services and contol
Inputsys_t rovides input device management and contol
Visuaisys_ = Provides visual system management and control,
Auaiosys_t Provides audio system management and contzl
Provides network system management and cor
Each system has an tnit(Systens_t *pSystons) and a Shutdown() method. Pass-
ing the systens_t pointer to the objects allows them to access any of the system inter-
faces. Including the systens_t class does not create compiler dependencies on the
systems code because the systems are accessed through pointers that only require for-
ward references. This is important, as Systons_t pointers are used in many of the
framework’s classes. Reducing physical dependencies is an important goal of good
physical design {Lakos96).
Because each of these systems is defined as a pure interface chat hides all imple
mentation details, we can dynamically switch system implementations as long as
those implementations do not have static link dependencies. The break-up of depen
dent implementations into dynamically loadable components is an example of the
packages patcern [Noble01].1.2 _An Object-Composition Game Framework
‘The source code on the CD-ROM demonstrates how to dynamically switch
visual systems. This is done using dynamic link libraries, each of which provides di
ferent implementations of the Visualsys_t interface. Here is an example of how to
contol the switch of visual systems:
FactorySys_t *pFS = m_psystems->setFactorySys();
pFS->DeletaVisualsys( m_pSystens->GetVisualsys() );
pFS.>SetVisualsysDriveri0( m_nVisualSysDriverI0 );
n_psystens->SetVisualsys( pFS->CreateVisualsys() );
Tasks
The Tasksyst class provides an interface to the task system. Using the
Post_TaskConnand function, we can post a task as either a frame-synchronized task or
an asynchronous task. The only difference is when the tasks are called. Frame-syn-
chronized tasks are called when the frame system reports that i is time to run the next
frame. Asynchronous tasks are called in each loop of the task system.
Post_TaskConmand allows us to add and remove tasks at any time, Here is an
example of how to post task commands to stop the current asynchronous task and
start a frame-synchronized task:
I/ get the task system
TaskSys_t *pTaskSys = m_pSystens->GetTaskSys();
J/ get the resource system
Rescurcesys_t *pResSys = a_pSystens->GetResourceSys();
I/ remove the current asynchronous task
pTaskSys-PPost_TaskCommand( ASYNC_REMOVE, this );
get the new task to start
Task_t *pTask = pResSys->GetTask( INSTANCE_ID TASK INTRO );
I push back the new frane-synchronized task
pTasksys->Post_TaskCommand( FRAMESYNC_PUSH BACK, pTask );
Here we see that we can access any task using its instance ID by calling
the resource system's GetTask function. Tasks are passed to the Systens_t pointer
‘when they are connected to the system via the task’s Connect (Systens_t *pSystens)
function.
Layers
Throughout the game development process, chere is typically a great amount of work
associated to visual rendering design. Rendering can be managed in a high-level man-
ner using a layer system. The importance of a layer system comes into play when the
game screen is rendered in a layered fashion. For example, during a 3D game, we
‘ight have the world rendered as one layer and perhaps game objects as another layer,
and then a “heads-up display” as a third layer. We want the ability to push new layers
‘onto the scene and have them appropriately affect visuals, audio, and input-handling
logic.
“To conteol layers of visual screens, audio data, and input-handling logic, we intro-Section 1 General Programming
duce a special kind of task called the FranePlayer_t. This task is intended to be
frame-synchronized and manages audio-visual-layer (AVLayer_t) objects and logic-
layer (Logictayer_t) objects
‘Audio-visual-layer objects are called each frame as follows:
1) Update AV Layers
for( each audio-visual layer (forward-order) )
{
AVLayer_t *PAVL = contents of iterator;
PAVL->Update()
,
11 Begin Render Visual
Af( m_pSystems->GetVisualsys()->BeginRender() )
{
for( each audio-visual layer (forward-order) )
4
AVLayer_t *pAVL = contents of iterator;
AVL->Réndervisual.();
»
'8_pSystems->GetVisualsys()->EndRender() ;
d
1 End Render Visual
11 Begin Render Audio
AF ( mupsystens->Getaudiosys()->BeginRender() )
for( each audio-vi
{
jal layer (forward-order) )
AVLayer_t *pAVL = contents of iterator;
pAVL->RanderAudio();
)
11_pSystems->GetAudiosys()->EndRender() ;
d
11 End Render Audio
"We can see that each audio-visual layer is updated first wich an Update() call.
This is when the audio-visual objects are updated, depending on their state of anima-
tion, Following this update call, we render the visuals and audio.
Logic-layer objects are called cach frame as follows:
J1 Update Logic Layers
for( each logic layer (reverse-order) )
4
Logiclayer_t *pLL = contents of iterator;
pLL->Update();
if( pLL->TsExclusive() ) break;
)
‘We can see that each logic layer is simply updated with an Update() call. While
audio-visual-layer updates are meant to handle animation, logic-layer updates are
used to handle game logic and player input.1-2. An Object-Composition Game Framework
Since the logic layers are processed in reverse, and since they stop processing if
they’re marked exclusive, the last logic layer in che m_LogicLayerPtrList can “over-
ride” previous logic layers. So, pushing back a logic layer can exclusively change the
way player input is handled, such as is required for game menus, viewers, and editors.
In contrast, if we push forward a new audio-visual layer, we only add a new set of
items to display and hear.
Just as the task syscem has task commands, the FranePlayer_t class has layer
commands. Here is an example of how to post layer commands:
AVLayer_t *pAVL;
Logictayer_t *pLL;
11 get the resource systen
Resourcesys_t *pResSys = m_pSystems->GetResourcesys();
J/ get the task to modity
Task_t *pTask = pResSys->GetTask{ INSTANCE_ID_TASK_INTRO) ;
I/we know it is a frane player
FramePlayer_t *pFP = (FranePlayer_t *)pTask;
1) push back an audio-visual layer
PAVL = pResSys->GetAVLayer (INSTANCE_IO_AVLAYER_INTRO) ;
FP->Post_AVLayarConmand (PUSH_BACK, AVL) ;
7/ push back a logic layer
pLL = pRosSys->GetLogiclayer (INSTANCE_ID_LOGICLAYER_INTRO) ;
pFP->Post_LogicLayerComnand (PUSH_BACK, pLL);
Here we see that we can get any audio-visual layer using its instance ID by calling.
the resource system's GotaVLayer Function. Similarly, we can get any logic layer using
its instance ID by calling the resource system's GotLogicLayer function. Layers are
passed to the Systons_t poincer when they are connected to the system via the layer’
Connect Systens_t *pSystens function.
With this layer system, we can now change audio-visual layering dynamically. All
of the various game modes and screens mentioned in the wrapping stage can be
implemented using layer and task commands. For example, we can push back
an audio-visual layer for a “heads-up display” when needed. Similarly, we can push
back audio-visual and logic layers for floating menus. When creating new modes and
screens, we have the option of editing the layering of our frame-player task using the
layer commands, or we can switch between frame-player tasks using the task com-
mands mentioned earlier.
Finally, a sophisticated use of layer and ask manipulation is in transitions. Items
‘on one game screen (the first audio-visual layer) can be gradually covered by items of
another game screen (the second audio-visual layer). When the transition is complete
and only the second layer is visible, the first layer can be removed from audio-visual
processing.
Source Code
The CD-ROM includes source code to the game framework implementation and
ove some additional documentation. The code with this book is meant to highlight gameis
Section 1 General Programming
framework concepts and not game technology concepts. Code updates will be avail-
able at http://www.gamefgramework.com. Figure 1.2.1 illustrates che modes, tasks,
and layers thar are implemented in the source.
se os assovealians Logetaye
Om Py
om IR ees
P83
©
367 ca08 ope, —
_— pO
- -—1_-@
Ou
0 vn
ome om
[pm amncaree(@)
Lf swencore =
Le —-©
ee
i
ae
Py
aa a
rm
@ co
“—-@
FIGURE 1.2.1 Using she game framework: An example ofhow game modes can be
implemented using tasks, audio-visual layers, and logic layers.[Boer00} Boer, James, “Object-Oriented Programming and Design “Techniques,”
Game Programming Gems, Chatles River Media, Inc., 2000: pp. 8-19.
[Bocr00] Boer, James, *Using the STL in Game Programming,” Game Programming
Gems, Chatles River Media, Inc., 2000: pp. 41-55
[GoF94] Gamma, E., et al., Design Pasrerns: Elements of Reusable Object-Oriented
Sofiware, Addison Wesley Longman, Inc., 1994.
{Lakos96] Lakos, John, Large-Scale C+ + Software Design, Addison Wesley Longman,
Inc. 1996.
[Llopis01] Llopis, Nocl, “Programming with Abstract Interfaces,” Game Program-
‘ming Gems 2, Charles River Media, Inc., 2001: pp. 20-27
[Meyers96] Meyers, Scott, More Effective C++, Addison Wesley Longman, Inc., 1996.
[Meyers98] Meyers, Scott, Effcrive C++, Second Edition, Addison Wesley Longman,
Inc., 1998.
[Meyers01] Meyers, Scott, Effctive STL, Addison Wesley Longman, Inc., 2001.
[Noble01] Noble, james, Small Memory Software: Patterns for Systems with Limited
‘Memory, Addison Wesley Longman, Inc., 2001
{Stroustrup97] Scroustrup, Bjarne, The C++ Programming Language, Third Edition,
‘Addison Wesley Longman, Inc., 1997.
(Vlissides98] Vlissides, John, Pattern Hatching: Design Patterns Applied, Addison Wes-
ey Longman, Inc., 1998.Finding Redeeming Value in
C-Style Macros
Steve Rabin, Nintendo of America, Inc.
steve@aiwisdom.com
the lowly C-style macro (#define) is a powerful construct that is severely misun-
derstood. Many people and books characterize it as evil and outdated, replaced by
the inline functionality in C++. Unfortunately, that’s a simplistic view that doesn't
take into account the unique functionality that che macro possesses. Shamefully,
many C++ books fail to explain the basic properties of macros or even acknowledge
their existence. However, solid explanations can still be found in classic C books such
as (Kernighan88}.
In a nutshell, the macro is a directive to the compiler’s preprocessor to do some
creative text replacement. Thus, no type-checking or other safety checks take place,
and this is where many programmers get into trouble. As a simple rule, macros
shouldn't be used to create function like behavior or constants. Fora discussion of the
pitfalls of macros, please refer to (Dalton01}, {Fiyman99], and (McConnell93).
However, this gem isn't about the problems with macros; it about proving that
macros are useful and desirable in many surprising ways. Macros allow you to per-
form fancy text replacement before actual compilation, and that’s exactly what the fol-
CE > _ lowing tricks exploic. Note that the source code that appears in each example can also
eité@ he found on the CD-ROM.
‘With macros, is easy to be carried away into a world of bad programming practices.
However, thats not the intention of this gem. While individuals have their own
boundaries as co what is acceptable, it is generally understood that making new
dialects of ClC++ by using macros is undesirable. Anyone coming onto a project
should be able to read your code with a minimal amount of effort. So, as you read
these tricks, understand that each one must be carefully weighed for its benefit versus
the possibility of obfuscation. Confined use of these examples is probably the most
reasonable way to benefit from this gem.1.3 Finding Redeeming Value in C-Style Macros 27
Macro Trick #1: Turning Enums into Strings
‘There are two special operators that let you do some tricky text manipulation inside
‘ofa macro. The first operator is #, and it will instruct the macro to put quotes around
the argument that follows it. For exampl
Aaetine CaseEnun(a) case(a): LogMsgToFile(#a, id, tine)
switch( msq_passed_in )
«
caseEnum( WSG_YouWlereHit
jeactToHst() ;
break;
yn( MSG_GanoReset );
etLogic();
break;
}
‘After the compilers preprocessing step, the previous cade becomes:
switch( msq_passed_in )
«
case( MSG_YouWereHit )
LogisgToFile( "MSG YouNereHit", id, tine );
ReactToHit ();
break;
case( MSG_GaneReset ):
LogisaToFile( ‘MSG GamaReset", id, tine );
ResetLogio();
break;
y
With this macro scheme, you can easily dump enum names in a reliable and
meaningful way, as actual strings, to a log file or the screen. Without this trick, you
would need co have a lookup table of all enums-to-strings. Unfortunately, lookup
tables are often poorly maintained and, thus, not reliable. The other solution is to use
strings in the first place, instead of enums, but ir’ doubsful you would wane to per-
form routine string compares inside your game. Therefore, the macto trick is a sound
solution that is both fast and reliable.
‘Another variant isto read in the enumeration from a header file and interpret the
single lise into both an enumeration and an array of string names. The following
shows how this can be done:
1) data.h
DATA(USG_YouvlereHit)
DATA(WSG_GameReset)
DATA(USG_Heal thRestored)