I code my games in C++ using visual studio 2015, and some help from visual assist from whole tomato (basically improved intellisense). I coded my own engine, but as GSSG is a simple 2D space shooter, thats easily good enough. I thought just in case anyone who reads my blog is learning C++, it might be interesting to describe some of the code.
The core part of the game is a function called GameProc, which is what gets called from the WinMain function in a loop, assuming the game is running, and its super simple:
void Game::GameProc()
{
GetInput()->Process();
GUI_GetSounds()->Process();
GGame::GameProc();
}
Thats the whole game loop! But obviously most of the relevant stuff happens in other classes. The basic principle is important though. My games reads all asynch user input (basically its checking the keystates for the keyboard), then it processes the sound engine, then the core game does its thing in a separate class. User input from mouseclicks and key hits is handled differently. I go through the windows messages for my app, and handle them as they happen outside this loop.
The fun stuff happens in that second GameProc function, which looks like this:
void GGame::GameProc()
{
HRESULT result = GetD3DEngine()->GetDevice()->TestCooperativeLevel();
if (FAILED(result))
{
if (result == D3DERR_DEVICELOST)
{
Sleep(50);
return;
}
else
{
RecoverGraphicsEngine();
}
}
if(PCurrentGameMode)
{
PCurrentGameMode->ProcessInput();
}
if (BActive)
{
GetD3DEngine()->BeginRender();
if(PCurrentGameMode)
{
PCurrentGameMode->Draw();
}
GetD3DEngine()->EndRender();
GetD3DEngine()->Flip();
}
else
{
ReleaseResources();
}
}
This is more interesting! Lets go through it. This code first checks to see if we somehow have lost the focus of the graphics driver, and if we have, it just pauses for 50ms and checks back later. Ideally everything then recovers from losing directx, and gets rebuilt. This sort of stuff isn’t really a problem now, as everyone is using non-exclusive borderless windowed mode, so its kinda legacy. The main game stuff comes next. The current game mode reacts to input, then assuming the game is still running, we begin the scene, draw everything and then end the scene, copying the backbuffer to the screen with flip.
So where is all the actual game code I hear you ask?
The trick is that PCurrentGameMode pointer. This is a pointer to an object thast represents the current game mode, and which one is selected and current is based on what we are doing. Right now my game has one object for the main menu, one for the (debug only) level editor, and one for the main game class. To make it interesting, lets check out the code for the main game objects call to Draw():
void GUI_Game::Draw()
{
SIM_GetGameplay()->Process();
GetD3DEngine()->ClearScreen(RGBA_MAKE(0, 0, 0, 255));
GetD3DEngine()->ClipViewport(GetGame()->ScreenArea);
if (GetGame()->GetGameModeName() == "game")
{
SetRT("rt_offscreen");
GUI_GetBackground()->Draw();
if (SIM_GetGameplay()->GetGameMode() != SIM_Gameplay::PREGAME)
{
SIM_GetShipManager()->Draw();
GUI_GetAsteroids()->Draw();
SIM_GetBulletManager()->Draw();
SIM_GetPowerupManager()->Draw();
GUI_GetParticleManager()->Update();
GUI_GetParticleManager()->Draw();
GUI_GetFloaterManager()->Draw();
GUI_GetShieldStrengths()->Draw();
GUI_GetDropLabels()->DrawAll();
}
PostProcess();
GUI_GetInterface()->Draw();
switch (SIM_GetGameplay()->GetGameMode())
{
case SIM_Gameplay::GAMEOVER:
PGameOver->Draw();
break;
case SIM_Gameplay::POST_LEVEL:
break;
case SIM_Gameplay::PREGAME:
DrawPreGame();
break;
}
GUI_GetWindowManager()->Draw();
if (BPaused)
{
DrawPaused();
}
}
GetD3DEngine()->RestoreViewport();
DrawBorders();
}
There is a lot of hacky nonsense happening here, but this is just a little hobby game, so I’m not too ashamed :D. So what does this do? Well the very first line of code does all of the actual gameplay stuff. I have an object of class type SIM_Gameplay, and I call that here and do all of the game simulation stuff. This moves the alien ships, handles scores, collision detection, and anything like that. All of the game mechanics are processed here, neatly separate from the graphics code.
Then I clear the screen to black, and clip the viewport (where we render) to an area I defined to be the gameplay screen. This is not the full screen, because I’m fixing the aspect ratio for this game to be some multiple of 1920×1080. This is the ‘ScreenArea’ which is just a RECT structure.
Then I get a bit clever. I set the render target to be an offscreen copy of the backbuffer I called rt-Offscreen. This is where I do 90% of the drawing in the game. I then go through a bunch of various singletons which access different visuals objects that get drawn, from back to front in painter-algorithm style, no Z buffer needed.
Finally I call PostProcess(). This is where I handle some fancy shockwave effects. I fill up yet another offscreen buffer with special images to donate any visual distortions I want to have, for when ships have shockwave explosions. I then copy the whole of that rt_offscreen to the backbuffer, using a shader which combines it with the contents of the distortion buffer to give me a nice distorted shimmer effect. Then finally I set the new render target to be the backbuffer, and draw the UI overlay stuff normally, so its NOT distorted by my shimmer effect.
Then I have some hacky places where I draw certain UI elements if the game is over, or not started yet, and then any windowed stuff, and finally some hacky code to draw GAME PAUSED if relevant.
Finally I restore the viewport so that I can fill in any surrounding borders for unusual aspect ratios and not have anything ‘leak’ out onto the edges.
This code is all a bit messy, because I haven’t nicely settled on a naming convention for a lot of those functions. Am I calling Draw() or Update() or DrawAll() its kinda random! Plus that UI stuff thats on the end of that function is a mess. I’m handling things THREE different ways here! An enum (GameMode) to call different functions, a complete window manager UI PLUS a special case there for if the game is paused. What a mess!
It all works and feels bug free, but its not clearly software engineered at all. I will definitely go back and re-arrange stuff and re-factor it so everything is laid out nicely. The reason I do NOT code like that at the start of the project is because I often throw things in quickly to see if they are a good idea, and I don’t want to type out a whole bunch of complex engineering layout baggage just to discover that this is a bad game mechanic or that this thing looks awful :D.
This is just the way I code, it doesn’t make it officially good, or fast, or better, its just what works for me!