Aplicación Práctica - Inicialización - Inicializando tu juego y sus subsistemas (Cacheado)


El siguiente código es copiado diréctamente desde una función en nuestro proyecto. Es llamado tan pronto como la función main() comienza (actualmente es la primera cosa que llamo), y lanza Ogre y establece unas pocas opciones. También hacer referencias que luego usaremos pero he querido copiarlo todo aquí para discutirlo.

        // un puntero al objeto Root de Ogre
 
        // El primer parametro es el nombre del archivo cfg de los plugins, el segundo es el nombre del archivo de cfg de ogre
 
        // no los usamos aqui, asi que dejamos cadenas vacias para que Ogre no sepa como cargarlos
 
        // El tercer parametro es el nombre del archivo de diagnostico Ogre.log; dejalo al valor por defecto
 
    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();
 
 
 
        // comprueba a traves de la lista de renderizadores disponibles, uno que contenga
 
        // la cadena en "val" (opcion "renderSystem" en el fichero config.ini)
 
        bool renderSystemFound = false;
 
        for (r_it=renderSystems->begin(); r_it!=renderSystems->end(); r_it++) {
 
            RenderSystem *tmp = *r_it;
 
            std::string rName(tmp->getName());
 
 
 
            // devuelve -1 si no se encuentra la cadena
 
            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 es un modo sencillo de hacer esto
 
        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");
 
 
 
        // falso porque no estamos usando una ventana autocreada
 
        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);
 
    }

Si, lo sé, todas estos literales de cadena deberían ser constantes estáticas. Para los propósitos de demostración he decidido dejarlos como literales de cadena.

Alrededor de la mitad de lo que ves esta hecho bajo la cubierta si quieres que Ogre compruebe estas opciones mediante restoreConfig(), y (b) llamar al método initialise() con "true" en vez de con "false". Sin embargo, no querrás distribuir tu juego con el diálogo de configuración de Ogre, y no querrás usar un archivo "ogre.cfg" para las opciones de video. De hecho, te gustaría usar un único archivo de opciones de configuración para cualquier cosa relacionada con los subsistemas, correcto? Así que necesitas hacer todo esto a mano. No es un gran trabajo, en realidad, sólo familiarizarte con el STL de C++ ya debería valerte.
ResourceGroupManager::getSingleton().addResourceLocation(
 
            "resource", "FileSystem", "General");
 
        ResourceGroupManager::getSingleton().addResourceLocation(
 
            "resource/gui.zip", "Zip", "GUI");
El código anterior comienza estableciendo el ResourceManager con sus grupos y localizaciones de rutas/ficheros. La primera localización de recursos que establecemos es el directorio de "recursos" bajo nuestra raíz de instalación del juego. Para esta serie, por convención colocaremos todos nuestros datos de recursos del juego dentro y bajo este directorio. Añadiendo esta localización dejamos que el ResourceGroupManager de Ogre sepa donde encontrar nuestro material.

Deberías preguntarte en este punto, "qué significa el ResourceGroupManager? para qué son útiles los grupos de recursos? El sistema de manejo de recursos de Ogre te permite colocar diferentes recursos en diferentes grupos para que asi puedas cargar y descargarlos de una manera más organizada. Si tu juego necesita esta capacidad. Como puedes ver en la siguiente línea del código anterior, inicializamos nuestro paquete de contenido GUI en el grupo de recursos "GUI"; posteriormente, si lo deseamos, podemos descargar sólo los recursos GUI del sistema, y podemos cargar otros recursos por nombre de grupo más tarde. Los grupos de recursos también realizan la misma función que los namespaces en C++: Tienes idénticos nombres de recursos en diferentes grupos de recursos y no pueden coincidir (en otras palabras, Ogre fallará).

Dos notas sobre nuestra carga de grupo de recursos: Primero, no cargamos OgreCore.zip. No lo necesitamos, esta aquí para ser usado por las aplicaciones de demostración, pero no es necesario para Ogre. Segundo, ponemos todo nuestro contenido GUI en un archivo gui.zip. Esto no es demasiado, pero podemos salvar algo de espacio; es solamente por conveniencia. Los ejemplos de aplicación CEGUI usan una estructura explícita de directorios bajo datafiles/ pero no significa que tú tengas que usarla.

Nota: Para familiarizarte con CEGUI, deberías usar la estructura de directorio datafiles/. Si miras en nuestro gui.zip, deberías encontrar los mismos archivos (TaharezLook.scheme, etc) pero con todos ../datafiles/.. eliminados. Honestamente no se porque CE hizo esto.

El grupo "General" es el lugar donde Ogre mirará por cualquier cosa que no haya sido calificada explícitamente con un nombre de grupo de recursos.

Configuración


La siguiente sección de código trata con la lectura de fichero config.ini para el juego (puedes llamarlo cuando quieras, es enteramente a tu elección).

VideoOptions es un typedef de un STL std::map que el parser de configuración getOptions() devuelve. Por una afortunada coincidencia, esto también resulta ser verdadero para Ogre::NameValuePairList, así que puedes ver como procesamos unas pocas opciones de configuración individuales (estas se pasaran a createRenderWindow() eliminaremos aquellos de la lista que sólo podamos pasar el std::map con sus contenidos restantes a createRenderWindow(). Quién dijo que los programadores son perezosos?. Ten en mente que lo que estamos haciendo es la forma que tienes de usar nombre de opción en tu .ini exáctamente como Ogre los espera.

En tu archivo de cabecera, asegúrate de añadir el siguiente typedef:
typedef NameValuePairList VideoOptions;
Para referencia, aquí esta la sección de video de nuestro archivo de configuración:
[video]
 
FSAA=0
 
colourDepth=32
 
fullscreen=false
 
renderSystem=Direct3D9
 
resolution=800x600
 
vsync=false

Date cuenta de como determinamos que sistema de renderizado usar. Hemos guardado un valor que es parte del nombre del Sistema de Renderizado de Ogre (Direct3D, OpenGL, etc). Buscaremos ese valor en el nombre de cada Sistema de Renderizado devuelto por getAvailableRenderers() y si se encuentra, establecemos el sistema de renderizado a usar. Yo elegí usar el nombre como aparece en nuestras aplicaciones de hojas de opciones de GUI (paciencia, paciencia) pero puedes usar la que quieras.

Una vez que hemos elegido un sistema de renderizado, debemos sentirnos libres de crear la ventana principal de Ogre. El resto de este método sencillo crea el manejador de escena que usaremos para la GUI y retorna. Nota: showGui() simplemente selecciona el manejador de escena que sólo tenemos que establecer mediante getSceneManager(); en este punto tendremos una ventana de renderizado en negro.

NOTA: DEBERÍAS QUERER QUE EN LA MAYORÍA DE LAS OCASIONES LA OPCIÓN DE PANTALLA COMPLETA ESTUVIERA A FALSE DURANTE EL DESARROLLO Y MIENTRAS LA DEPURACIÓN; UN FALLO AL HACER ESTO RESULTARÁ EN TENER QUE REINICIAR TAN PRONTO COMO TU PROGRAMA LLEGUE AL BREAKPOINT PORQUE NO TENDRÁ CONTROL SOBRE LA PANTALLA.

FWIW, getOptions() es una función que utiliza el API Win32 SHGetFolderPath() o la variable $HOME en Linux (como se discutió en el artículo anterior) así como en Win32 GetPrivateProfileSection() o la opción en Linux para leer secciones del archivo config. Los contenidos son entonces pasados en un mapa de VideoOptions. Aquí está el código para getOptions():

#ifdef WIN32
 
#include <shlobj.h>
 
#else
 
#endif
 
 
bool getOptions(VideoOptions opts)
 
{
    // lee esto desde el archivo config ... en Win32 tenemos una bonita
 
    // API para leer archivos config; en otras plataformas necesitaremos hacerlo nosotros
 
    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;
 
 
    // si, se que este no es el modo correcto de manejar esta situacion.
 
    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;
 
}


Necesitarás incluir "shlobj.h" para los entornos WIN32.

La constante define CONFIG_OPTS_DIR debería ser algo como ".mygame"; que es el nombre del directorio en el espacio de appsettings de usuario dondo quieres mantener tus opciones. CONFIG_FILE_NAME en nuestro caso es "config.ini"; el tuyo podría ser otro, es de tu elección. Usamos principalmente un archivo "punto" porque es la convención en Linux, y a Windows le da igual.

Cada aplicación es diferente, pero la mayoría quieren guardar un conjunto de inicialización de seguridad que pueda ser usado para proporcionar un entorno inicial seguro en caso que el usuario haya borrado su config. Como veremos más tarde, en el manejador de plantillas de Opciones GUI, si el usuario no tiene un config.ini crearemos uno y automáticamente usaremos los valores por defecto. Actualmente leemos nuestros valores por defecto de un script Lua; deberías empaquetarlos todos en un recurso de cadena DLL, o dentro de tu aplicación. Como con todos los diseños software, no hay una solución única para todos los casos.