Practical Application - Initialization         Initializing your game and its subsystems

The following code is pasted directly from a function in our project. It is called almost as soon as the main() function starts (actually, I just looked, this is the first thing called), and fires up Ogre and sets a few options for it. It also references stuff that we'll get to later in the series but I wanted to paste the whole thing here for discussion.

//wrangle a pointer to the Root Ogre object 
        // the first param is the name of the plugins cfg file, the second is the name of the ogre cfg file
        // we are not using either here, so provide them as empty strings to let Ogre know not to load them
        // The third param is the name of the Ogre.log diagnostic file; leave it default for now
    ogre = new Root("", "");

    try {
        ResourceGroupManager::getSingleton().addResourceLocation(
            "resource", "FileSystem", "General");
        ResourceGroupManager::getSingleton().addResourceLocation(
            "resource/gui.zip", "Zip", "GUI");

        VideoOptions opts;
        VideoOptions::iterator it;
        getOptions(opts);
        std::string val;
        unsigned int h, w;
        bool fullscreen = false;
        Ogre::RenderSystemList *renderSystems = NULL;
        Ogre::RenderSystemList::iterator r_it;

        val = opts.find("renderSystem")->second;
        renderSystems = ogre->getAvailableRenderers();

        // check through the list of available renderers, looking for the one that contains
        // the string in "val" ('renderSystem' option from the config.ini file)
        bool renderSystemFound = false;
        for (r_it=renderSystems->begin(); r_it!=renderSystems->end(); r_it++) {
            RenderSystem *tmp = *r_it;
            std::string rName(tmp->getName());

            // returns -1 if string not found
            if ((int) rName.find(val) >= 0) {
                ogre->setRenderSystem(*r_it);
                renderSystemFound = true;
                break;
            }
        }

        if (!renderSystemFound) {
            throw new VideoInitializationException("Specified render system (" + val + ") not found, exiting...");
        }

        // sscanf is the easy way to do this
        val = opts.find("resolution")->second;
        sscanf(val.c_str(), "%dx%d", &w, &h);
        opts.erase("resolution");

        val = opts.find("fullscreen")->second;
        if (val == "true")
            fullscreen = true;
        opts.erase("fullscreen");

        // false because we are not using an autocreated window
        ogre->initialise(false);
        window = ogre->createRenderWindow(appName, w, h, fullscreen, &opts);

        ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

        guiSceneMgr = ogre->createSceneManager(ST_GENERIC);
        showGui();
    }
    catch (Ogre::Exception &e) {
        std::string msg = e.getFullDescription();
        std::cerr << msg << std::endl;
        exit (-1);
    }


Yes, I know, all of those string literals should be static constants. :p For purposes of demonstration I elected to leave them as string literals, though.

About half of what you see above is done under the covers if you (a) let Ogre parse its options via restoreConfig(), and (b) call the intialise() method with "true" instead of "false". However, you do not want to distribute your game with the Ogre config dialog popping up all the time, nor do you want to use an "ogre.cfg" filename for video options. In fact, you'd like to use a single options file for everything related to your subsystems, right? So you need to do all that stuff manually. No big deal, really, just be familiar with the C++ {LEX()}STL{LEX} and you should be fine.

ResourceGroupManager::getSingleton().addResourceLocation(
            "resource", "FileSystem", "General");
        ResourceGroupManager::getSingleton().addResourceLocation(
            "resource/gui.zip", "Zip", "GUI");


The code above starts off by setting up the ResourceManager with its groups and file/path locations. The first resource location we set is the "resource" directory under our game's installation root. For this series, by convention we will place all of our game's resource data in and under this directory. Adding this location lets Ogre ResourceGroupManager know where to find our stuff.

You might ask at this point, "what is meant by ResourceGroupManager? What are resource groups useful for?" Ogre's resource management system allows you to place different resources in different groups so that you can load and unload them in a more organized manner. Whether your game needs this capability is entirely up to you. As you see in the next line of code above, we initialize our {LEX()}GUI{LEX} content package into the "GUI" resource group; later, if we wish, we can unload just the GUI resources from the system, and we can load other resources by group name later as well. Resource Groups also perform the same function as namespaces in C++: You may have identical resource names in different resource groups and they will not clash (in other words, cause Ogre to crash).

Two notes about Ogre's resource management system: (1) It will not recurse into subdirectories unless you tell it, and (2) folder names are meaningless even if you do. This means that you cannot have identical filenames in different folders in, for example, a Filesystem or Zip resource location type, and expect Ogre to like that.

Note two things about our resource group loading: First, we do not load OgreCore.zip. You don't need it, it's there for use by the demo applications, but nothing in there is actually needed by Ogre itself. Second, we put all of our GUI content into a gui.zip file. This is not so much so that we can save space or anything like that; it's a simple matter of convenience. The {LEX()}CEGUI{LEX} sample apps use an explicit and verbose directory structure under datafiles/, but that doesn't mean you have to as well.

Side note: For those familiar with CEGUI, you may be used to the datafiles/ directory structure. If you looked inside our gui.zip, you would find the same files (TaharezLook.scheme, etc) but with all of the ../datafiles/... bits stripped out. I honestly don't know why CE did this, and he may not either, but you should save yourself a lot of headache in the long run and remove this relatively absolute (*snicker*) directory information from your CEGUI config files.

The "General" group is the place where Ogre will look for anything not explicitly qualified with a resource group name.

Configuration

The next section of code deals with reading the config.ini file for the game (you can call it whatever you want, it's entirely up to you).

VideoOptions is a typedef of an STL std::map that the getOptions() config parser function returns. By a lucky coincidence, this also happens to be true of the Ogre::NameValuePairList, so you see as we process a select few individual config options (the ones that we will pass to createRenderWindow()) we remove those from the list so that we can just pass the std::map with its remaining contents to createRenderWindow() as well. Who said programmers aren't lazy? ;) Keep in mind that doing it this way will require you to use option names in your .ini exactly as Ogre expects them.

In your header file, be sure to add the following typedef:

typedef NameValuePairList VideoOptions;


For reference, here is the video section of our config file (at present, anyway):

[video]
FSAA=0
colourDepth=32
fullscreen=false
renderSystem=Direct3D9
resolution=800x600
vsync=false


Notice how we determine which render system to use. We have stored a value that is part of the Ogre RenderSystem name ({LEX()}Direct3D{LEX}, {LEX()}OpenGL{LEX}, etc). We'll look for that value in the name of each RenderSystem returned by getAvailableRenderers() and if found, set that render system for use. I chose to use the name as it appears in our app's GUI Options sheet (patience, patience ;)) but you can use whatever you want, so long as you can figure out how to convert what it means into how the render systems are named in Ogre.

Once we've chosen a render system, we are free to create the main Ogre window and be on our merry way. The rest of this method simply creates the scene manager that we will use for the GUI (PATIENCE I SAID! :p ) and returns. Note: showGui() simply selects the scene manager we just set with getSceneManager(); at this point we just have a black render window.

NOTE: YOU WILL ALMOST ALWAYS WANT TO SET FULLSCREEN TO FALSE DURING DEVELOPMENT AND WHILE DEBUGGING; FAILURE TO DO SO WILL RESULT IN YOU HAVING TO REBOOT AS SOON AS YOUR PROGRAM HITS A BREAKPOINT BECAUSE YOU DON'T HAVE CONTROL OF YOUR SCREEN

FWIW, getOptions() is simply a function or method that utilizes the Win32 SHGetFolderPath() {LEX()}API{LEX} or the $HOME variable on Linux (as discussed in a previous article) as well as the Win32 GetPrivateProfileSection() or (I don't know what, it hasn't been written yet) on Linux to read sections of the config file. The contents are then parsed into the VideoOptions map. Here is the code for getOptions():

#ifdef WIN32
#include <shlobj.h>
#else
#endif

bool getOptions(VideoOptions opts)
{
    // read these from the ubiquitous config file...on Win32 we have a nice handy
    // API to read config files; on other platforms we'll need to fake one
    char path[MAX_PATH+1];

#ifdef WIN32
    SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path);
#else
#endif
    
    std::string pathname(path);
    pathname += "/" + CONFIG_OPTS_DIR + "/" + CONFIG_FILE_NAME;

#ifdef WIN32 
    DWORD nSize = 1024, rtnSize;
    char strVal[1024], *cp = strVal;

    // yes I know this is not the right way to handle this situation...sue me. :p
    rtnSize = GetPrivateProfileSection("video", strVal, nSize, pathname.c_str());
    if (rtnSize == nSize - 2)
        throw new VideoInitializationException("Cannot read video settings - buffer too small");
    if (rtnSize == 0)
        return false;

    std::string name, val;

    opts.clear();
    while (*cp != 0 && *(cp+1) != 0) {
        name = cp;
        val = cp;
        cp += strlen(cp) + 1;

        name = name.substr(0, name.find('='));
        val = val.substr(val.find('=') + 1);

        opts.insert(VideoOptions::value_type(name, val));
    }
#else
#endif

    return true;
}


You'll need to include "shlobj.h" for WIN32 environments.

The constant define CONFIG_OPTS_DIR would be something like ".mygame"; it's the name of the directory in the user's appsettings space where you want to keep your options. CONFIG_FILE_NAME in our case is "config.ini"; yours may differ, it's entirely up to you. We use a "dot" filename mainly because it's the convention on Linux, and Windows doesn't care either way.

Every application is different, but you'll almost always want to store a set of failsafe defaults that can be used to provide a known startup environment in the case that your user has deleted their config, or if theirs is missing. As we'll see later, in the Options GUI sheet handler, if the user doesn't have a config.ini we'll create one for them automatically using reasonable defaults. Currently we read our defaults from a {LEX()}Lua{LEX} script; you may pack them into a string resource {LEX()}DLL{LEX}, or hardcode them into your application, whatever suits your needs. As with all software design, there is no single "one-size-fits-all" solution.

Prev <<< Practical Application - FoundationPractical Application - GUI >>> Next