Developer experiences from the trenches
Mon 28 April 2014 by Michael Labbe
tags code
So you’ve made a super fun multiplayer game and you want to expand it by adding game modes, tweaks and so forth. Here are some possible do’s and don’ts that allow for rapid expansion and moddability.
The approach of branching your game logic by game modes makes your code unexpandable. Consider:
if ( gamemode == MODE_DEATHMATCH ) {
DisplayFreeForAllScoreboard();
} else if ( gamemode == MODE_CTF || gamemode == MODE_TEAMDM ) {
DisplayTeamScoreboard();
}
This works with a finite number of game modes, but it makes modding hard. It also makes expanding the number of game modes difficult. A better approach is to create an abstract game rules base class which is derived for each game mode.
class IGameRules
{
public:
virtual int GetTeamCount( void ) const = 0;
};
class GameRulesDeathmatch : public IGameRules
{
public:
int GetTeamCount( void ) const {
return 1;
}
};
class GameRulesCTF : public IGameRules
{
public:
int GetTeamCount( void ) const {
return 2;
}
};
Now you can simply write the game logic as:
if ( gamemode.GetTeamCount() == 1 ) {
DisplayFreeForAllScoreboard();
} else if ( gamemode.GetTeamCount() == 2 ) {
DisplayTeamScoreboard();
}
This approach lets you easily extend your game code to new game mode variants by simply deriving from the IGameRules
hierarchy.
Unreal gets credit for being the first game to use modifiers. Modifiers are tweaks to tuning values — they are not holistic mods. This lets the player apply, for example, low gravity and instagib at the same time, by selecting two different mods. (Thereby ruining my vanilla experience, grr…)
This is pretty simple: apply modifiers from top-to-bottom order. Call out conflicts. Unreal did modifier selection with a complete UI in the late 90s.
Consider exposing game rules methods as modifier tunable values. For example, if you have a game rule bool IsElimination()
, which causes players to not respawn after they die, exposing this as a modifier value will allow a modder to go in and take an existing game mode, say, Team DM, and turn it into an elimination mode. Boom! A modder just recreated a simple Clan Arena mode with a text file and no need to learn a scripting language.
Mon 07 April 2014 by Michael Labbe
tags code
When something is not right in your game’s simulation, complaining loudly to the developer-culprit as early as possible roots out issues. Production code that tolerates failure at a data processing level while producing a completely errant play experience saves no time.
If a developer commits errant content and time goes by, the cost of fixing it goes up. The developer may have forgotten the intricacies of their contribution, or worse, be on a different project. The content may need to be regenerated from source files in a manner that is either unclear or is not available to the person who deals with the bug. The symptoms of the bug may be disconnected from the problem. For example, I have seen a non-normative bitrate in an audio file corrupt a stack, becoming a time consuming issue to track down.
When you realize a subtle warning was added to a programmer-facing debug log that stated the issue, but was ignored by the developer who added the file, it is time to look for better solutions.
Throwing assert messages when an invalid programmatic state is hit is a common practice for trapping code logic errors. Extending this diagnostic trip-up to content issues for non-programmers is a useful tool for getting in developers faces at the right moment in time — when the developer is trying out their new content for the first time.
What can a content alert do for you?
Provide validation that content is consistent with the engine’s expectations. For example, if a PNG has a corrupt header, there is nothing wrong with the PNG loader logic. It’s just dealing with questionable data. Sure, it could probably display something if the rest of the file is structured properly, but this is indicative of a bad file and you need to get this up in developer faces before they move on to other challenges.
Test code that runs as soon as possible. If you have a cooking stage that runs over your content, throw your alerts then. If you do not, do it at level load. Validate everything.
A way of passing a diagnostic message on to the content creator as soon as possible. Short circuit the QA/bug tracker loop for content creators (level designers, artists, audio engineers). This saves time by providing the opportunity for a specific diagnostic message that gets to the root of the issue. Bug reports from QA describe the symptom of the issue and usually lack direct diagnostic messages. This is much quicker.
Resource r = LoadResource();
if ( r.GetBPP() != 32 )
ContentFail( "Resource has invalid bitdepth" );
ContentFail
is a preprocessor macro which, in developer-friendly builds, accumulates a descriptive list of issues for the content creator.
You know which builds are going to developers and which are going to the end user. Use conditional compilation to optionally throw a message up in front of the user.
#if ENABLE_CONTENT_DIAGNOSTICS
#define ContentFail(msg) (void)(HandleContentFail( __FILE__, __LINE__, msg ) )
#else
#define ContentFail(msg) ((void)0)
#endif
void HandleContentFail(
const char *file,
int line,
const char *failmsg )
{
/* Append failmsg to diagnostic list here */
}
In this implementation, ENABLE_CONTENT_DIAGNOSTICS
is on for all builds going to developers and, presumably, off for ship. You accumulate a list of issues and push them to a dialog box after a level load, treat them as compile warnings in a build process or purposely sound an alarm in the cook process.
One benefit of compiling out the content asserts in release builds is avoiding the fear that you are adding a ton of diagnostic strings to shipping code. Go nuts here — be as descriptive and as helpful as possible.
None of this is particularly fancy, complicated or tricky to implement in any language. It amounts to adopting a philosophy of enforcing correctness as early as possible in the design of your tools.
Edit: thanks to @datgame for pointing out a bug in the example code. It has been fixed!
Mon 05 December 2011 by Michael Labbe
tags code
Scripting languages have really fallen out of favor in modern game development. It’s easy to rile a working programmer up to a lather about poor debuggers, lack of threading or slow compile times related to a scripting language they were saddled with on a given project.
That said, the pendulum has swung too far in the anti-scripting direction.
The beginning of a console cycle is epitomized by a need for compatibility: “how can we ship on multiple prototype hardware platforms?” and tool efficiency: “how can we reduce the spiralling cost of development inherent in expanding horizons?” These questions led developers to make scripting plays in their engines.
In contrast, we are near the traditional end of a console cycle. We understand how to get a lot out of the hardware so the focus is free to move towards code execution efficiency in order to exceed the experiences players have had on the fixed hardware to date.
Looking past the consoles, memory latency is forcing us towards cache-friendly approaches to implementation which avoids indirection inherent in runtime bytecode interpretation and vtable lookups. Even if we push the CPU Ghz up, we aren’t going back to peppering our runtimes with vtables anytime soon thanks to memory latency overhead.
In this environment, it’s fully understandable that we would deign to avoid developing in languages like UnrealScript where only the rote engine-level loops are not bytecode interpreted. None of this means that scripting languages should be cut out of game development.
I see two places where scripting still beats out compiled code by providing new experiences for players:
First, scripting can be for one-off level scripting events. Scripts will always have a place as glue code. If you are nesting your loops in a level script, you have probably gone too far. Because you are not attempting to implement large parts of the game world in script and beacuse they do not run on tick, the performance impact is minimal.
A few lines of script is a natural alternative to abstract event firing systems. Visual scripting languages, ironically, are rarely informative at a glance. It’s much easier to see all of the code in one place than to click around a 3D blueprint looking for wires between entities and still need to guess at execution order.
The second use for scripts is more interesting. Right now, a big trend in games is towards online.
If you send state updates to your clients, the only way to add new behavior to your game is to patch your client. If you send script code for interpretation, you are only limited by the assets in the local cache and the hardcoded simulation to what your players experience.
The first version of Capture the Flag for Quake was written by Zoid before QuakeWorld. He took advantage of the fact that, before QuakeWorld, the Quake client was mostly a dumb terminal sending a subset of QuakeC over the pipe. Players could connect to his unique server without any mods and play CTF. This low barrier helped make Threewave Capture the Flag the first really popular Internet teamplay game.
If you are sending script over the pipe in 2011, please remember to fuzz your interpreter… and don’t drop in an off-the-shelf language that wasn’t designed with unsafe code in mind. Thanks.
Mon 09 August 2010 by Michael Labbe
tags code
Hardware is the basis of understanding rendering. Not numerical problems, not geometric problems and certainly not memorizing OpenGL or Direct3D APIs.
One of the most first questions that needs to be asked when deciding to implement new graphical features is what needs to be implemented on the general purpose CPU and what can be computed on a GPU (or even a set of SPEs). This question is impossible to answer without a fundamental understanding of the hardware you are programming for. The abstraction of your favorite API does less to mask this as more programmatic options become available. Consider:
Once you can competently speak on these points, you can start to devise a hypothesis about how to best divide your hardware resources to render a typical scene for your game.
At this point, you have a shot at guessing what data needs to be where in your pipeline and when it needs to be there. This is the point when numerical and geometric issues move into focus.
OpenGL (with the exception of OpenGL ES) is a pool of functions, many of which superficially achieve the same results but with different approaches to dealing with bus latency. As memory latency becomes more of an acute problem in paring down hardware implemented pipeline stalls, literature promoting immediate mode and display lists continues to be the most prominent information on OpenGL in spite of the strong need for sizable data batches.
I’m always going to be a fan of getting stuff up on the screen quickly and programming some sample apps in OpenGL does give you something to mentally referenece when learning theory, but you aren’t programming anything really interesting until you’ve understood the graphics pipeline and at least the timeline of how hardware acceleration has crept backwards through the graphics pipeline over the past ten years.
Jim Blinn’s Corner: A Trip Down the Graphics Pipeline - An old one that covers software rendering, but gives you a basis of understanding of the graphical pipeline.
Real-Time Rendering - Currently in 3rd edition. The modern replacement for Foley and VanDam. The chapters on performance and hardware combine with the fundamental understanding of the graphics pipeline to help you really understand what’s going on.
Technical briefs for hardware prepared by NVidia and ATI for target hardware. This information is made available on their respective sites.