0% found this document useful (0 votes)
119 views618 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.

Uploaded by

livemonitor2017
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
0% found this document useful (0 votes)
119 views618 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.

Uploaded by

livemonitor2017
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
You are on page 1/ 618
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 Board 19 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 Weber 27 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 Shankel vill 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 477 SECTIONS 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 587 6.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 [email protected], [email protected] ‘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 2 ccuted. 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 no 4.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 is Scheduling 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 addi 412 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 [email protected] [email protected] 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 Function Dynamic 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 game is 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. [email protected] 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)

You might also like