Tuesday, 12 June 2012

Understanding CryEngine 3 code: Starting the game

I'm starting to look at the programming side of the CryEngine 3 Free SDK. I've been quite surprised to see that the official documentation gives little to no information on the overarching concepts and systems that are present in the engine. I then decided go down the brutal path to understand the system, which is putting breakpoints (almost) everywhere, and see what happens, in which order.

As usual when I do that kind of self-teaching, I find that writing up the results of my investigation helps me making sure I know what I'm talking about. And it might as well be useful to other people.

DISCLAIMER: In this series of articles, I'm not explaining how to make your own game in CryEngine 3. I'll stick to describing what happens (or seems to happen) in the code and what conclusions I draw from it. Also, I'm assuming that you know how to code in C++.

Today, I'm interested in the initialisation of the game application: from starting the Launcher.exe up to the beginning of the main loop.

CGameStartup

So, the one thing the documentation tells us is that the appropriately named CGameStartup class is the one that handles the initialisation of the game. When you run the game, the following functions of that class are called (in order).

CGameStartup::Init()

Well, the name is self explanatory, it does some initialisation. The first thing to note is the call to a function named InitFramework(). At the time of writing, I haven't quite figured out what's the role of the framework is apart from the fact that (as the name suggest) it's the foundation of the application. When we look at that function's body, we can see that it is possible to tell the game to load the framework from a different DLL, though I'm not sure it's possible for the free SDK users. In the free SDK, the GameFramework is the CCryAction class, living in the CryAction DLL for which we only have access to the header files. It is when the framework is initialised that the game window is created.

Back to the Init() function, the other interesting thing is the following bit of code:
// load the appropriate game/mod
const ICmdLineArg *pModArg = pSystem->GetICmdLine()->FindArg(eCLAT_Pre,"MOD");

IGameRef pOut;
if (pModArg && (*pModArg->GetValue() != 0) && (pSystem->IsMODValid(pModArg->GetValue())))
{
 const char* pModName = pModArg->GetValue();
 assert(pModName);
 pOut = Reset(pModName);
}
else
{
 pOut = Reset(GAME_NAME);
}
It's looking for a command line argument specifying a mod. If there is one and it's valid, we call CGameStartup::Reset(), passing the name of the mod as an argument. if there's no mod to load, we call the same function, with another argument (by default, GAME_NAME is "CryENGINE3").

There's our first lesson about CryEngine 3's concepts: as far as the engine is concerned, a "proper" game and a mod are exactly the same thing. The only difference is that a DLL containing a mod doesn't need the CGameStartup class, and that the "proper" game's DLL will be loaded regardless of the situation.

CGameStartup::Reset()

This function does the following:
  • If there's an active mod/game, shut it down (obviously won't be the case on a first startup).
  • If we need to start a mod, load the DLL.
  • Create and initialise an instance of the game class (we'll get back to that later).
After that, it returns to the Init() function, which does a few more things (notably loading localized data) before returning to the Launcher code.

CGameStartup::Run()

After the Reset() function has ended, some behind the scenes stuff happens and you get asked for your CryDev login information. Then, CGameStartup::Run() is called and that's where things really start.

The game runs a console command that loads a file called autoexec.cfg, which I'm assuming will do some global settings initialisation.

Then, there's an interesting piece of code:
if (autoStartLevelName)
{
 //load savegame
 if(CryStringUtils::stristr(autoStartLevelName, ".CRYSISJMSF") != 0 )
 {
  CryFixedStringT<256> fileName (autoStartLevelName);
  // NOTE! two step trimming is intended!
  fileName.Trim(" ");  // first:  remove enclosing spaces (outside ")
  fileName.Trim("\""); // second: remove potential enclosing "
  gEnv->pGame->GetIGameFramework()->LoadGame(fileName.c_str());
 }
 else //start specified level
 {
  CryFixedStringT<256> mapCmd ("map ");
  mapCmd+=autoStartLevelName;
  gEnv->pConsole->ExecuteString(mapCmd.c_str());
 }
}

autoStartLevelName is a parameter passed to the Run() function (though at the time of writing, I don't know where that value comes from). As the code chunk above suggests, this can either be a gamesave name, or the name of a level.

There, we learn that loading a save file (and most likely making one) is handled by the GameFramework (i.e. a black box, as far as our current knowledge goes), and that map loading/change is done through a console command.

When that is done (if it's done), we enter the infinite loop, which at some point calls the Update() function.

CGameStartup::Update()

The only interesting thing happening here is a call to the game class' Update() function.

CGame

We've seen that the game instance gets created during CGameStartup::Reset().

CGame::CGame()

The constructor does a number of interesting instantiations:
  • CGameActions: A class that defines action filters. An action is the result of pressing a gamepad button / keyboard key or moving a joystick / mouse. An action filter is a list of ignored actions.
  • CCameraManager: pretty much self explanatory. I will probably dedicate an article to this class (and the related ones), so I won't talk about it here.
  • CGameMecanismManager:  I haven't quite figured out what a game mechanism is in CryEngine (the only visible implementation is the FeatureTester), but it sounds important. Again, this will probably have a dedicated article. Some day.

CGame::Init()

This one does a bunch of stuff, but I'll just linger on a few things.

Early in the function, action maps are loaded (the file that defines which button triggers which action) and merged with the user's custom control mapping.

Later, the GameFactory is initialised, via  CGame::InitGameFactory(). This function is doing a bunch of registration, associating a name with a C++ class or a lua file. That seems to be a very important part of the system, but I have yet to explore it a bit more to gain a proper understanding of it. I'm making a note here and will get back to that another day.

Lastly, I want to highlight following code chunk:

if ( gEnv->pScaleformGFx && gEnv->pScaleformGFx->IsScaleformSupported() )
{
 CUIManager::Init();
}
else
{
 if ( m_pBitmapUi == NULL )
 {
  m_pBitmapUi = new CBitmapUi();
 }
}

This, as you may expect, is loading the GUI. AS you can see, Scaleform GUI is handled by the CUIManager class, while "old school" GUI is handled by CBitmapUI. In the sample game provided with the SDK, remove that chunk and you get no frontend. You now know where to look at if you need to change the main menu.

CGame::Update()

This surprisingly doesn't do much apart from updating game audio, that mysterious Game Mechanism Manager and the Weapon System. I suspect that it's the game framework that does the heavy lifting (whatever there is to lift).

And that's it, once you reach that point, the game is loaded and running. Next time, I'll take a look at what happens when you load a level.