Aplicación Práctica - Comencemos - Después de planear nuestra aplicación, comencemos a escribirla


Esta es una aplicación mínima que haga lo mismo que el Tutorial Básico 1: lanzar una ventana sin nada y esperar que pulses ESCAPE, para terminar la aplicación.

En este punto hemos examinado cuales son las partes principales en las que se basa una aplicación Ogre, y los problemas de implementación. Ahora, iremos más allá y escribiremos una. Comencemos con lo básico. Comenzaremos con una ininterrumpida lista de código.

El Código


main()

main.cpp


#include "input.h"
 
#include "simulation.h"
 
#include "Ogre.h"
 
#include "OgreWindowEventUtilities.h"
 
 
#if defined(WIN32)
 
#include "windows.h"
 
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
 
#else
 
int main (int argc, char *argv[]) {
 
#endif
 
 
    Ogre::Root *ogre;
 
    Ogre::RenderWindow *window;
 
    Ogre::SceneManager *sceneMgr;
 
    Ogre::Camera *camera;
 
 
    // lanza una ventana de renderizado de Ogre. Limpiando primero dos (o tres paramentros) que nos permitiran
 
    // especificar los plugins y los recursos por codigo en vez de mediante un archivo de texto.
 
    ogre = new Ogre::Root("", "");
 
 
    // Este es un ejemplo minimo de la carga de un sistema de renderizado; estamos usando el renderizado OpenGL,
 
    // en vez de cargar GL y D3D9. Anyadiremos la seleccion del sistema de renderizado en un articulo futuro.
 
 
    // Separo las versiones de depuracion y las finales de mis plugins usando el mismo sufijo "_d" que
 
    // las librerias principales de Ogre usan; necesitaras eliminar "_d" en tu codigo, dependiendo de
 
    // la convencion de nombres que uses.
 
    // NOTA PARA EIHORT: Todas DLLs de Ogre usan esta convencion de sufijo -- #ifdef en la base de _DEBUG
 
    // define
 
#if defined(_DEBUG)
 
    ogre->loadPlugin("RenderSystem_GL_d");
 
#else
 
    ogre->loadPlugin("RenderSystem_GL");
 
#endif
 
 
    /*
 
    ----------------------------------------------------------------------------
 
    [Nota]: Si estas usando Ogre 1.7 las siguientes 4 lineas de codigo (sin incluir los comentarios)
 
            deberian ser cambiadas a las siguientes 2 lineas:
 
 
    const Ogre::RenderSystemList &renderSystem = ogre->getAvailableRenderers();
 
    Ogre::RenderSystemList::const_iterator &r_it = renderSystem.begin();
 
 
    [Final Nota]
 
    ----------------------------------------------------------------------------
 
    */
 
    Ogre::RenderSystemList *renderSystems = NULL;
 
    Ogre::RenderSystemList::iterator r_it;
 
    // hacemos este paso solo para conseguir un iterador que podemos usar con setRenderSystem. En un articulo futuro
 
    // ahora iteramos la lista a mostrar con los sistemas de renderizado disponibles.
 
    renderSystems = ogre->getAvailableRenderers();
 
    r_it = renderSystems->begin();
 
    ogre->setRenderSystem(*r_it);
 
    ogre->initialise(false);
 
    // carga los plugins comunes
 
#if defined(_DEBUG)
 
    ogre->loadPlugin("Plugin_CgProgramManager_d");
 
    ogre->loadPlugin("Plugin_OctreeSceneManager_d");
 
#else
 
    ogre->loadPlugin("Plugin_CgProgramManager");
 
    ogre->loadPlugin("Plugin_OctreeSceneManager");
 
#endif
 
    // load the basic resource location(s)
 
    Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
 
        "resource", "FileSystem", "General");
 
    Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
 
        "resource/gui.zip", "Zip", "GUI");
 
#if defined(WIN32)
 
    Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
 
        "c:\\windows\\fonts", "FileSystem", "GUI");
 
#endif
 
    Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup("General");
 
    Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup("GUI");
 
    // establece la ventana principal; codifica algunas opciones por defecto para la presentacion
 
    Ogre::NameValuePairList opts;
 
    opts["resolution"] = "1024x768";
 
    opts["fullscreen"] = "false";
 
    opts["vsync"] = "false";
 
 
    // crea una ventana de renderizado con el titulo "CDK"
 
    window = ogre->createRenderWindow("CDK", 1024, 768, false, &opts);
 
 
    // ya que esto es basicamente una aplicacion CEGUI, podemos usar el manejador de escena ST_GENERIC; en un último artículo
 
    // veremos como cambiar esto
 
    sceneMgr = ogre->createSceneManager(Ogre::ST_GENERIC);
 
    camera = sceneMgr->createCamera("camera");
 
    camera->setNearClipDistance(5);
 
    Ogre::Viewport* vp = window->addViewport(camera);
 
    vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
 
 
    // la mayoria de los ejemplos consiguen el tamanyo del puerto de vista para calcular esto; por ahora, solo
 
    // establecemos el aspecto a 4:3 del modo mas facil.
 
    camera->setAspectRatio((Ogre::Real)1.333333);
 
 
    // el siguiente paso es para el manejo de la entrada
 
    unsigned long hWnd;
 
    window->getCustomAttribute("WINDOW", &hWnd);
 
 
    // establece los manejadores de entrada
 
    Simulation *sim = new Simulation();
 
    InputHandler *handler = new InputHandler(sim, hWnd);
 
    sim->requestStateChange(SIMULATION);
 
 
    while (sim->getCurrentState() != SHUTDOWN) {
 
        handler->capture();
 
 
        // ejecuta la cola de mensajes (Eihort)
 
        Ogre::WindowEventUtilities::messagePump();
 
 
        ogre->renderOneFrame();
 
    }
 
    // despues nos autolimpiamos
 
    delete handler;
 
    delete sim;
 
    delete ogre;
 
 
    return 0;
 
}


He extraido este codigo del contenido de un kit de desarrollo y lo he creado para nuestro proyecto de ahí su nombre "CDK" (content development kit). He cogido lo más útil e interesante.

Un rápido tour por el código nos muestra la inicialización, la instalación de las rutas de recursos, y el bucle principal. El bucle main (principal), como discutimos antes, también llama al sistema de mensajes de ventanas, que en Win32 activa el renderizado de ventanas con la posibilidad de usar combinaciones de teclas como: ALT+TAB. Como veremos más tarde también activa que la ventana muestre su contenido: sin esta cola de mensajes, el sistema de renderizado de ventanas de Ogre no conseguirá mandar mensajes de "pintar" (WM_PAINT en Win32, que activa la ventana para refrescar su contenido regularmente).

Hay otros pocos objetos con los que tener cuidado antes de construir tu código. Primero, hay unos pocos archivos que necesitaremos ver. Primero, los archivos de la declaración de "Simulation" (.h) y la definición (.cpp):

Simulation


simulation.h


#pragma once
 
#include <vector>
 
#include <map>
 
 
typedef enum {
 
    STARTUP,
 
    GUI,
 
    LOADING,
 
    CANCEL_LOADING,
 
    SIMULATION,
 
    SHUTDOWN
 
} SimulationState;
 
 
class Simulation {
 
 
public:
 
    Simulation();
 
    virtual ~Simulation();
 
public:
 
    bool requestStateChange(SimulationState state);
 
    bool lockState();
 
    bool unlockState();
 
    SimulationState getCurrentState();
 
 
    void setFrameTime(float ms);
 
    inline float getFrameTime() { return m_frame_time; }
 
 
protected:
 
    SimulationState m_state;
 
    bool m_locked;
 
    float m_frame_time;
 
};

simulation.cpp

#include "simulation.h"
 
#include "OgreStringConverter.h"
 
 
Simulation::Simulation() {
 
    m_state = STARTUP;
 
}
 
Simulation::~Simulation() {
 
}
 
SimulationState Simulation::getCurrentState() {
 
    return m_state;
 
}
 
// para conseguir mas claridad, no estoy usando sincronizacion de hilos
 
// de los objetos para serializar el acceso a este recurso. deberias querer proteger
 
// este bloque con un mutex o seccion critical, etc.
 
bool Simulation::lockState() {
 
    if (m_locked == false) {
 
        m_locked = true;
 
        return true;
 
    }
 
    else
 
        return false;
 
}
 
bool Simulation::unlockState() {
 
    if (m_locked == true) {
 
        m_locked = false;
 
        return true;
 
    }
 
    else
 
        return false;
 
}
 
bool Simulation::requestStateChange(SimulationState newState) {
 
    if (m_state == STARTUP) {
 
        m_locked = false;
 
        m_state = newState;
 
        return true;
 
    }
 
    // este estado no puede ser cambio una vez que se ha iniciado
 
    if (m_state == SHUTDOWN) {
 
        return false;
 
    }
 
    if ((m_state == GUI || m_state == SIMULATION || m_state == LOADING || m_state == CANCEL_LOADING) &&
 
            (newState != STARTUP) && (newState != m_state)) {
 
        m_state = newState;
 
        return true;
 
    }
 
    else
 
        return false;
 
}
 
void Simulation::setFrameTime(float ms) {
 
    m_frame_time = ms;
 
}

La clase "Simulation" es un ejemplo muy simple de una clase "state manager" (manejador de estado). Los estados de Simulation no son nada mas que diferentes contextos de ejecución, y en el interes de la ejecución ordenada de nuestro programa, deberías usarlos. Mostraremos más estados que necesitamos aquí, pero estos estados son todo usados por nuestro proyecto. No hay unos estados de juego oficiales; un estado es sólo lo que quieres definir que sea. Sin embargo, hay algunos estados muy típicos que todos los juegos y simulaciones comparten, SHUTDOWN, SIMULATION y GUI son 3 estados de juego típicos. SHUTDOWN informa a la aplicación que ha ocurrido un apagado (shutdown) (en nuestro caso usando la tecla ESCAPE o pulsando en la esquina superior derecha la "x" de cierre de la ventana de renderizado). SIMULATION es el estado que define una ejecución "normal" de nuestro juego (estamos ejecutando la simulación o procesando la lógica de un juego, actualizano y mostrando la escena 3D, etc). GUI es un estado de la interacción de usuario con una GUI 2D, como las soportadas por CEGUI. (Tenemos un estado separado GUI que será capaz de decir cuando NO inyectar la entrada a CEGUI, para prevenir la pérdida innecesaria de ciclos de CPU).

El manejador de entra en nuestro ejemplo usa OIS. Trataremos OIS en los archivos input.h/input.cpp:

InputHandler


input.h

#pragma once
 
#include "OISEvents.h"
 
#include "OISInputManager.h"
 
#include "OISMouse.h"
 
#include "OISKeyboard.h"
 
#include "OISJoyStick.h"
 
 
class Simulation;
 
 
class InputHandler :
 
        public OIS::MouseListener,
 
        public OIS::KeyListener,
 
        public OIS::JoyStickListener
 
{
 
private:
 
    OIS::InputManager *m_ois;
 
    OIS::Mouse *mMouse;
 
    OIS::Keyboard *mKeyboard;
 
    unsigned long m_hWnd;
 
    Simulation *m_simulation;
 
public:
 
    InputHandler(Simulation *sim, unsigned long hWnd);
 
    ~InputHandler();
 
 
    void setWindowExtents(int width, int height) ;
 
    void capture();
 
 
    // MouseListener
 
    bool mouseMoved(const OIS::MouseEvent &evt);
 
    bool mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID);
 
    bool mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID);
 
 
    // KeyListener
 
    bool keyPressed(const OIS::KeyEvent &evt);
 
    bool keyReleased(const OIS::KeyEvent &evt);
 
 
    // JoyStickListener
 
    bool buttonPressed(const OIS::JoyStickEvent &evt, int index);
 
    bool buttonReleased(const OIS::JoyStickEvent &evt, int index);
 
    bool axisMoved(const OIS::JoyStickEvent &evt, int index);
 
    bool povMoved(const OIS::JoyStickEvent &evt, int index);
 
};

input.cpp

#include "input.h"
 
#include "OgreStringConverter.h"
 
#include "simulation.h"
 
 
InputHandler::InputHandler(Simulation *sim, unsigned long hWnd)  {
 
    OIS::ParamList pl;
 
    pl.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(hWnd)));
 
 
    m_hWnd = hWnd;
 
    m_ois = OIS::InputManager::createInputSystem( pl );
 
    mMouse = static_cast<OIS::Mouse*>(m_ois->createInputObject( OIS::OISMouse, true ));
 
    mKeyboard = static_cast<OIS::Keyboard*>(m_ois->createInputObject( OIS::OISKeyboard, true));
 
    mMouse->setEventCallback(this);
 
    mKeyboard->setEventCallback(this);
 
 
    m_simulation = sim;
 
}
 
InputHandler::~InputHandler() {
 
    if (mMouse)
 
        m_ois->destroyInputObject(mMouse);
 
    if (mKeyboard)
 
        m_ois->destroyInputObject(mKeyboard);
 
    OIS::InputManager::destroyInputSystem(m_ois);
 
}
 
void InputHandler::capture() {
 
    mMouse->capture();
 
    mKeyboard->capture();
 
}
 
void  InputHandler::setWindowExtents(int width, int height){
 
    //Set Mouse Region.. if window resizes, we should alter this to reflect as well
 
    const OIS::MouseState &ms = mMouse->getMouseState();
 
    ms.width = width;
 
    ms.height = height;
 
}
 
// MouseListener
 
bool InputHandler::mouseMoved(const OIS::MouseEvent &evt) {
 
    return true;
 
}
 
bool InputHandler::mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID btn) {
 
    return true;
 
}
 
bool InputHandler::mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID btn) {
 
    return true;
 
}
 
// KeyListener
 
bool InputHandler::keyPressed(const OIS::KeyEvent &evt) {
 
    return true;
 
}
 
 
bool InputHandler::keyReleased(const OIS::KeyEvent &evt) {
 
    if (evt.key == OIS::KC_ESCAPE)
 
        m_simulation->requestStateChange(SHUTDOWN);
 
    return true;
 
}
 
// JoyStickListener
 
bool InputHandler::buttonPressed(const OIS::JoyStickEvent &evt, int index) {
 
    return true;
 
}
 
 
bool InputHandler::buttonReleased(const OIS::JoyStickEvent &evt, int index) {
 
    return true;
 
}
 
 
bool InputHandler::axisMoved(const OIS::JoyStickEvent &evt, int index) {
 
    return true;
 
}
 
 
bool InputHandler::povMoved(const OIS::JoyStickEvent &evt, int index) {
 
    return true;
 
}

Ya que la presión de la tecla ESCAPE es manejada en una clase separada, tenemos que dejar a la aplicación conocer que debería apagar. Hacemos esto mediante el estado simulation, y para hacer esto, necesitamos tener referencia a objeto controlador de simulación en el manejador de entrada. Hacemos esto pasando un puntero al objeto en el constructor del manejador de entrada, y guardándolo como un dato miembro de clase.

Este punto es importante, lo hacemos por claridad. A menudo verás objetos "Singleton" en Ogre. Son instancias de clase, en las que sólo puede haber una instancia (los detalles del patrón Singleton son mejor manejados).

Date cuenta de que no sólo cambiamos el estado, tenmos que "pedir cambiar el estado". Si una parte arbitraria de tu programa falla, que ocurriría al código si está ejecutando el contenido particular de un estado? Este problema ocurre más con programas multihilo que en monohilo, y en programas no triviales que usan multihilo. Si permitimos el cambio de las "peticiones", permitimos a secciones de código completar su operación satisfactoriamente, entonces el estado puede ser cambiado. En este ejemplo trivial, sólo cambiamos el estado de nuestra petición.

Operamos con OIS en modo con búffer así que podemos evitar la pérdida de eventos de entrada. Llamando a inputHandler::capture() y hacer llamadas a los métodos capture() del ratón y del teclado, los cuales vaciaran sus búferes de eventos pendientes y los distribuirán apropiadamente.

Dejo los eventos de joystick aquí para mostrar como se implementa el soporte de joystick. Sólo tenemos un único dato miembro (el puntero de dispositivo de joystick) que tiene soporte para el stick. No lo necesitamos ahora, pero será implementado en el siguiente artículo.

Construyendo y ejecutando el Código

Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
        "resource", "FileSystem", "General");
    Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
        "resource/gui.zip", "Zip", "GUI");
Como puedes ver en la sección de inicialización, definimos y llenamos dos grupos de recursos: General y GUI. General está siempre presente, y es el grupo de recursos por defecto de Ogre. GUI es un grupo que hemos creado para guardar el contenido de la GUI, y todo en el archivo gui.zip es cargado dentro de este grupo.

Para que esto funcione sin estropearse, debemos tener un directorio llamado "resource" que contenga un archivo zip llamado "gui.zip" en el directorio de trabajo de la aplicación. Si obtienes el archivo ZIP al final de este artículo, encontrarás esto en la carpeta de Debug/ después de descomprimir. Esto puedes hacerlo donde quieras, como puedes ver, nosotros referenciamos el directorio de fuentes de windows (ya verás el porqué en el próximo artículo).

Necesitamos también tener el include apropiado y los directorios de librería establecidos para esta compilación. Necesitarás conseguir OIS de la página de SourceForge anterior (a menos que uses el ZIP que está al final del artículo, que incluye OIS), y obviamente necesitarás las cabeceras de Ogre y las librerías (que vienen con el Ogre SDK). También necesitarás las dependencias de Ogre listadas en el sitio de Ogre si estas construyendo Ogre desde código (El Ogre SDK incluye las DLLs necesarias y sus cabeceras).

Asumo que sabes como establecer directorios en Visual C++ (o en tu IDE particular); si no, visita los documentos de tu IDE y regresa cuando lo sepas. Si tienes instalado el Ogre SDK, entonces no necesitas hacer nada de esto. Si lo has compilado desde código, necesitarás lo siguiente:

C++:
Additional Include Directories: (rutas a las cabeceras de Ogre y a las cabeceras de OIS.

Enlazador:
Additional Library Directories: (rutas a los archivos .lib de Debug en Ogre y a la libreria estatica de Debug de OIS)
 
Input: OgreMain_d.lib OIS_d.lib dxguid.lib dinput8.lib
También necesitarás establecer el directorio de trabajo bajo "Debugging" a la carpeta de Debug/. No he incluido el .suo que inexplicablemente contiene estos ajustes del proyecto; si obtienes errores al ejecutar de que no consigue encontrar el recurso, este es el problema: establece el directorio de trabajo apropiadamente y todo debería funcionar correctamente.

Necesitaremos las librerías DX porque OIS compila como una librería estática y depende de estas librerías. Date cuenta de que estamos usando las versiones de Debug de las libs; elimina "_d" si quieres las finales.

El proyecto VC++ es sólo un proyecto normal Win32 vacío. De manera alternativa, puedes desempaquetar el ZIP y sólo compilarlo (después de arreglar los directorios antes mencionados que refleja tu instalación). Yo he usado VC++ 2003 (VC 7.1) para compilar este código.

Paquete de Ejemplo de Código


Descarga el código para este artículo


[NOTA: Este código todavía compilará y se ejecutará con Ogre 1.7. Funcionará como se espera (una pantalla en negro de la que puedes salir con la tecla escape). El mayor cambio trata cuando usar CEGUI y lo cubriremos en la siguiente sección.]

Notas de Compilación y Ejecución

  • Este código ha sido actualizado para Eihort, y fue testado contra Ogre SDK 1.4 RC1 (Eihort) para VC71; esto significa que debería compilar y ejecutar contra cualquier instalación de SDK Ogre 1.4.x.
    • El fichero ZIP ha sido actualizado a Ogre 1.6.x e incluye los ficheros VC9 sln/vcproj
  • El código descargado es un archivo ZIP. Contiene el fuente de este artículo; ya que Eihort proporciona su propia versión de OIS como una Dependencia, no será necesario en la distribución del archivo.
  • Necesitarás instalar el SDK para compilar este código (a menos que compilado Ogre desde código. Si tienes instalado el Ogre SDK 1.4.x, deberías ser capaz de simplemente desempaquetar el archivo ZIP, abrir el PracticalApp.sln, y pulsar en "Build Solution" para la configuración Debug y lo tendrás completo sin problemas.
  • Querrás añadir $OGRE_HOME/bin/Debug a tu ruta antes de ejecutar el ejecutable PracticalApp; como hacer esto (cuando se testa, abro un terminal bash Cygwin y tecleo "export PATH=$OGRE_HOME\bin\Debug:$PATH" antes de ejecutar PracticalApp.exe desde la línea de comando de Bash -).
    • También puedes establecerlo en los ajustes de proyecto bajo Debugging; cualquiera de la variables de entorno que establezcas seran mezcladas con las variables que tiene Visual Studio. Los archivos de proyecto de VC9 también incluyen un archivo .vcproj que contiene estos ajustes, así que si has instalado el SDK o has instalado apropiadamente OGRE_HOME, de funcionará.