Someone
once said, "Experts are just people who have already made all the
mistakes in their field." If this old saying is remotely true,
I must be well on my way. After two years battling on the front lines
at Zombie, I look back on the creation of SpecOps as a crusader
looks back on victory in the Holy Land. Through the haze of past battles
won and lost, I will now try to remember what it was that we did right
and wrong, and how this product finally hit the shelves.
To begin,
let’s define the responsibilities of our game engine. Our game engine
manages many aspects of the game: sound, graphics, physics, game core,
and others. Taken alone, these components don't do anything — they’re
just tools. The game-specific AI uses these tools to perform logical
game actions. In this manner, a complete game is composed of the game
engine plus the game-specific AI.
As
such, the SpecOps game is composed of the Viper game engine and
the SpecOps AI and resources. In this article, I will primarily
focus on the design aspects of the Viper engine, which was the first
and most important step in creating SpecOps. In Table 1, you’ll
find a description of the project and its development environment. With
these design requirements in mind, let’s take a look at how each of
the major components of the game engine was implemented.
Game
Core
The game
core provides an interface for all of the game engine components. Among
other things, it defines and manages "objects" in the engine
and allows them to be passed between components. For example, the game
core might first send an object to the physics engine to be moved and
then to the graphics engine to be drawn.
In the
Viper engine, we chose to implement the objects hierarchically. Figure
1 shows a small portion of this hierarchy. The hierarchy allows memory
optimizations because objects only require allocation of the structures
that they use. It also provides some object-oriented structure to the
program. In C, there was one .C file for each object type, with the
same .H relationship as in Figure 1.
 |
Figure 1. Hierarchical
object relationship from SpecOps.
|
The only
objects that the game core referenced and modified were those at the
top of the hierarchy: Generic Object, Static Object, and Dynamic Object.
The rest of the hierarchy is composed of game-specific data structures
(such as the Character class). The support modules for these were separated
from the rest of the code, so that it would be easy to remove them from
the game engine code. This helped ensure the separation of game-specific
logic from the game engine.
This design
was expandable and easy to work with. While building the engine, we
could separate out the SpecOps-specific code to create demos
and even other games. Doing so at several points during the development
cycle forced us to continually clean up any breaches of the design and
maintain the reusability of the engine.
Game
Editor
0ur game
editor let us introduce new resources into the game engine, modify game
play, debug game logic, and print out diagnostics. An editor environment
needs to be easy to learn and use, so that the game designers and artists
can make game modifications without a programmers being involved. It
also needs to be continually maintained, providing access to new features
of the engine as they are created and having bugs fixed or the interface
modified to save time.
I wanted
the Viper editor to be totally integrated into the game engine. This
would help ensure that new game engine code didn’t break the editor
code, and would allow us to add new game engine features to the editor
environment. I created an EDITOR compiler switch that created an encapsulating
.DLL into the executable game file upon compilation. This .DLL ran the
game as a child thread, allowing us to suspend the execution of the
game and modify the contents of the game’s memory. With this switch,
we could activate functions in the game engine that we didn’t want in
the release version.
When active,
the Viper editor displays a simple console . Command strings can be
entered into this console to perform a variety of actions in the game
engine. For instance, we would frequently use this functionality to
load terrain geometry, create a binary space partition (BSP) tree of
that geometry, and save the BSP tree to disk in the native file format.
 |
The
Viper editor console.
|
Overall,
I was pretty happy with this editor. While it’s more cryptic than the
user interfaces of the Quake and Unreal editors, it has
several benefits that those systems don’t offer. First, all of our programmers
were able to add functionality to it. Once our team learned how to use
the editor’s string commands (which took about five minutes), any programmer
could add functions to support the code they wrote. Had I written a
MFC-based GUI editor, I would have been supporting the editor on full-time
basis because few team members were familiar with Windows code. Another
point in favor of our editor was the fact that our geometry was being
created separately by CAD tools, so we didn’t need geometry creation
facilities such as those found in the Quake and Unreal
editors. Simply put, our needs were different from those games, and
the command string interface fit our needs fairly well.
Yet, the
editor did have drawbacks. Some tasks were difficult to complete using
the command string interface. For example, to place dynamic objects
(such as enemies and pickups) in the environment effectively, the game
designers really needed a CAD-like, windowed interface that showed the
entire level at various angles and allowed designers to place objects
with mouse clicks. This could have been added fairly easily, but we
lacked the time to implement it. As we advance the Viper engine, I’d
like to see the editor seamlessly built into an existing CAD package,
such as 3D Studio Max. That way, we could take advantage of an interface
that the artists are already familiar with and draw upon pre-existing
features.