Also how do you go about coding a game?
Coding a game is a bit different than coding, say, a desktop application. Instead of sleeping and waiting for things to happen like a modern GUI application, games have a tight loop that does any number of things. At the bare minimum, the gameplay loop will check for input, update the game state, and update the screen. Ensuring that everything happens at the correct time is a pretty important thing, too. You know how some games lose their shit if you uncap the framerate? Yeah, that means they messed up the timing bit.
Game loops are generally fairly tight and typically don't handle more than the things I mentioned above and occasionally things like state changes. Here's a couple of practical examples of game loops:
StepMania,
Doom. Despite the difference in language and age, the two games do the same basic thing: they have a loop that ends until a set condition is met to handle the player quitting the game. Let's take a quick run through StepMania's loop:
while( !ArchHooks::UserQuit() )
This is the start of the loop proper. It says to loop through everything contained in the curly braces until
ArchHooks::UserQuit() returns
true (literally, the statement reads to process the block while
UserQuit() is
false).
if(!g_NewGame.empty())
{
DoChangeGame();
}
if(!g_NewTheme.empty())
{
DoChangeTheme();
}
These two conditionals check for two state changes: game changes (i.e. dance, pump, techno, etc) and theme changes. If either of these conditions happen, it calls to the appropriate function to load in the new data before continuing the loop.
float fDeltaTime = g_GameplayTimer.GetDeltaTime();
if( g_fConstantUpdateDeltaSeconds > 0 )
fDeltaTime = g_fConstantUpdateDeltaSeconds;
This call first retrieves the amount of time that has passed since the last time the gameplay loop was run. The delta time variable will be passed to the subsequent update functions. This is important for stuff like animations, as transitions on a 60hz monitor will have to move further than transitions on a 144hz monitor per frame in order to complete at the same time.
The conditional just below that checks to see if constant update ticks are enabled. This is an experimental feature designed to smooth out animations if the game drops frames. If that's enabled, it uses that as the delta time instead.
Personally, I would have shortened that to this, but that's just me:
float fDeltaTime = g_fConstantUpdateDeltaSeconds > 0
? g_fConstantUpdateDeltaSeconds
: g_GameplayTimer.GetDeltaTime();CheckGameLoopTimerSkips( fDeltaTime );
This is a pretty important one for an accuracy-focused game like StepMania. This call points to a function that checks for dropped frames and logs them if it encounters them.
This is an optional feature and can be disabled from the INI file. If it's disabled, this call really doesn't do much of anything (jumps into the function, then almost immediately returns).
fDeltaTime *= g_fUpdateRate;
This is the command that allows the magic tilde and tab commands to work. Normally,
g_fUpdateRate is set to 1, so this statement does nothing. However, if you're holding tab, it gets set to 4. If you hold tilde, it gets set to 0.25. If you're holding both, it gets set to 0. Since
fDeltaTime impacts animations and such, the delta time getting increased or decreased will cause them to play faster or slower since those functions will think that more or less time passed than it actually did. This is also why tab/tilde work regardless of framerate: it takes the delta time and modifies it rather than selectively bypassing the timing routines (which is what a lot of emulators do).
CheckFocus();
This checks to see if the game window is in focus. This will cause the game to drop all inputs to ensure that keys don't get stuck. Additionally, it also changes the game's priority accordingly, so StepMania won't use as many of your system resources if it loses focus.
// Update SOUNDMAN early (before any RageSound::GetPosition calls), to flush position data.
SOUNDMAN->Update();
/* Update song beat information -before- calling update on all the classes that
* depend on it. If you don't do this first, the classes are all acting on old
* information and will lag. (but no longer fatally, due to timestamping -glenn) */
SOUND->Update( fDeltaTime );
TEXTUREMAN->Update( fDeltaTime );
GAMESTATE->Update( fDeltaTime );
SCREENMAN->Update( fDeltaTime );
MEMCARDMAN->Update();
NSMAN->Update( fDeltaTime );
The specifics of these update calls are out of the scope of this text (it would take quite a bit of digging to cover them), but this is what updates the game state, animations, and sound subsystems each frame.
/* Important: Process input AFTER updating game logic, or input will be
* acting on song beat from last frame */
HandleInputEvents( fDeltaTime );
Input events are handled after the game objects are updated. Placing the input between the game logic update and the draw routines ensures that the input that you press applies to the frame that's being drawn and will ensure consistency. This is critical, especially for timing-sensitive games.
One thing that's easy to overlook is that drawing assets to the screen takes some time (barely perceptible, but it's there). If you're updating consistently at 60hz, each update can take up to 16.666 milliseconds. What's worse is that drawing can take a different amount of time depending on how dense the chart is—MAX 300 at 8x is going to take much less time to draw than bag at 0.25x, for example. If we placed the input routines above the game logic updates, that variable delay would be applied to your input. For StepMania, that could mean the difference between a Fantastic and an Excellent, and it would be impossible to predict. Frame drops can still potentially affect input, but this method at least ensures that timing will be consistent as long as you hold a consistent framerate.
if( INPUTMAN->DevicesChanged() )
{
INPUTFILTER->Reset(); // fix "buttons stuck" if button held while unplugged
INPUTMAN->LoadDrivers();
std::string sMessage;
if( INPUTMAPPER->CheckForChangedInputDevicesAndRemap(sMessage) )
SCREENMAN->SystemMessage( sMessage );
}
This is the block that allows you to plug in and unplug a controller and have StepMania pick it up without requiring a restart. The reset method (which is also called by
CheckFocus() above) forcibly releases all inputs. If you're standing on a dance pad while unplugging it, it prevents the inputs that you were standing on from sticking and causing problems after the device disappears.
The rest of the block loads the appropriate input drivers, then logs and/or auto-remaps the controls to suit the new device.
LIGHTSMAN->Update( fDeltaTime );
If you're running StepMania on a converted cabinet or set up your own lighting rig, this causes the lights to fire.
SCREENMAN->Draw();
And, finally, assemble the scene and draw the results.
While there's a lot of magic that happens on top of this (changing screens, playing muzak, etc) this is the loop that orchestrates the entire process. This essentially directs whichever screen is active to do its thing. Think of a game as being a tree. At the base you have the game loop. Below that you have the various major screens in the game (for StepMania, it's stuff like ScreenGameplay, ScreenEvaluation, ScreenSelectMusic, etc). Then you have all of the individual object types that make up those screens, then the objects that make up those, et cetera, with each of those update calls flowing down the tree. This is how you can have a single, tiny game loop like this directing a complex piece of software.
When it's done this way it makes things feel a bit less daunting (by comparison, at least). The core engine sits at the bottom, acting as a conductor that tells the higher level objects when to feed it data and, if done correctly, how much. If you start to delve more into object-oriented programming, the way the pieces of the puzzle fit together will start to become clearer.
I'll put the non-programming stuff in another reply. This post has gotten kind of ridiculous.