Tutorial Básico 5 Entrada con Búfer


external image dl1773Introducción del Tutorial


En este corto tutorial aprenderemos como usar la entrada OIS con búfer en oposición a la entra sin búfer que vimos en el tutorial anterior. Este tutorial difiere del último en que manejaremos los eventos de teclado y ratón inmediatamente, cuando sucedan, en vez de una vez por frame. Nota que esto es sólo una introducción a la entrada con búfer, y no un tutorial completo de como usar OIS. Para más información sobre esto, asegúrate de mirar el artículo Usando OIS.

external image help.gifLos problemas que encuentres mientras trabajes en este tutorial deberías consultarlos en los Foros de Ayuda.

Prerrequisitos


  • Este tutorial asume que tienes conocimientos sobre programación en C++ y eres capaz de compilar e instalar una aplicación de Ogre.
  • Este tutorial también asume que has creado un proyecto usando el Tutorial Framework Ogre Wiki, manualmente, usando CMake o el AppWizard de Ogre - mirar Creando una aplicación para más instrucciones.
  • Este tutorial se compila sobre la versión anterior de los tutoriales básicos, así que se asume que has trabajado con ellos.

Cuando mires el tutorial deberías ir lentamente añadiendo código a tu proyecto y mirando los resultados. Puedes ver el código fuente para ver el estado final del tutorial aquí. Si tienes problemas con el código, deberías comparar tu fuente del proyecto al resultado final.

Comenzando


Este tutorial se construye sobre el último, pero cambiamos el modo en que tomamos la entrada. Ya que la funcionalidad será básicamente la misma, usaremos la misma clase BaseApplication de la última vez. Crea un proyecto en el compilador de tu elección para este proyecto, y añade el siguiente código:

Cabecera BasicTutorial05
#ifndef BasicTutorial05_h_
#define BasicTutorial05_h_
 
#include "BaseApplication.h"
 
class BasicTutorial05 : public BaseApplication
{
public:
     BasicTutorial05(void);
     virtual ~BasicTutorial05(void);
 
protected:
     virtual void createScene(void);
     virtual void createFrameListener(void);
 
     // Ogre::FrameListener
     virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt );
     // OIS::KeyListener
     virtual bool keyPressed( const OIS::KeyEvent& evt );
     virtual bool keyReleased( const OIS::KeyEvent& evt );
     // OIS::MouseListener
     virtual bool mouseMoved( const OIS::MouseEvent& evt );
     virtual bool mousePressed( const OIS::MouseEvent& evt, OIS::MouseButtonID id );
     virtual bool mouseReleased( const OIS::MouseEvent& evt, OIS::MouseButtonID id );
 
          Ogre::Real mRotate;                 // Constante de rotacion
          Ogre::Real mMove;                   // Constante de movimiento
          Ogre::SceneNode *mCamNode;          // El SceneNode la camara que esta acoplada en este momento
          Ogre::Vector3 mDirection;           // Valor para moverse en la direccion correcta
};
 
#endif #ifndef BasicTutorial05_h_

Implementación BasicTutorial05

#include "BasicTutorial05.h"
 
//-------------------------------------------------------------------------------------
 
BasicTutorial05::BasicTutorial05(void)
{
}
//-------------------------------------------------------------------------------------
 
BasicTutorial05::~BasicTutorial05(void)
{
}
 
//-------------------------------------------------------------------------------------
void BasicTutorial05::createScene(void)
{
             mSceneMgr->setAmbientLight(Ogre::ColourValue(0.25, 0.25, 0.25));
 
             // anyade el ninja
             Ogre::Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");
             Ogre::SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
             node->attachObject(ent);
 
             //crea la luz
             Ogre::Light *light = mSceneMgr->createLight("Light1");
             light->setType(Ogre::Light::LT_POINT);
             light->setPosition(Ogre::Vector3(250, 150, 250));
             light->setDiffuseColour(Ogre::ColourValue::White);
             light->setSpecularColour(Ogre::ColourValue::White);
 
             // crea el SceneNode
             node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Ogre::Vector3(-400, 200, 400));
 
             // hace que apunte al ninja
             node->yaw(Ogre::Degree(-45));
 
             // crea el nodo de pitch
             node = node->createChildSceneNode("PitchNode1");
             node->attachObject(mCamera);
 
             // crea el segundo nodo de camara/nodo pitch
             node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Ogre::Vector3(0, 200, 400));
             node = node->createChildSceneNode("PitchNode2");
}
             void BasicTutorial05::createFrameListener(void){
                      BaseApplication::createFrameListener();
}
             bool BasicTutorial05::frameRenderingQueued(const Ogre::FrameEvent& evt){
return true;}
 
             // OIS::KeyListener
             bool BasicTutorial05::keyPressed( const OIS::KeyEvent& evt ){return true;}
             bool BasicTutorial05::keyReleased( const OIS::KeyEvent& evt ){return true;}
             // OIS::MouseListener
             bool BasicTutorial05::mouseMoved( const OIS::MouseEvent& evt ){return true;}
             bool BasicTutorial05::mousePressed( const OIS::MouseEvent& evt, OIS::MouseButtonID id ){return true;}
             bool BasicTutorial05::mouseReleased( const OIS::MouseEvent& evt, OIS::MouseButtonID id ){return true;}
 
 
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
 
#ifdef cplusplus
extern "C" {
#endif
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
    int main(int argc, char *argv[])
#endif
{
        // Crea un objeto de aplicacion
        BasicTutorial05 app;
 
        try {
             app.go();
} catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
        MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        std::cerr << "An exception has occured: " <<
             e.getFullDescription().c_str() << std::endl;
#endif
}
 
        return 0;
}
 
#ifdef __cplusplus
}
#endif

El programa controla lo mismo que en el tutorial anterior. Sobreescribiremos los métodos frameRenderingQueued y createFrameListener, al igual que los métodos de evento MouseListener y KeyboardListener (keyPressed(), keyReleased(), mouseMoved(), mousePressed() y mouseReleased).

Entrada con Búfer


Introducción


En el tutorial anterior usamos la entrada sin búfer, esto es que, cada frame pide el estado de las instancias OIS::Keyboard y OIS::Mouse para ver que teclas y botones del ratón estan presionados. La entrada con búfer usa interfaces de listener para informar al programa que eventos han ocurrido. Por ejemplo, cuando una tecla esta presionada, un evento KeyListener::keyPressed es lanzado y cuando el botón se suelta un evento KeyListener::keyReleased es lanzado a todas las clases registradas de KeyListener. Esto tiene en cuenta la pista de los cambios o si la tecla fue soltada en el frame anterior.
OIS también soporta eventos de Joystick sin búfer a través de la interfaz OIS::JoystickListener interface, no hablaremos de como usar esto en este tutorial.

Una cosa importante que resaltar sobre el sistema de listener de OIS es que puede tener sólo un listener para teclado, ratón, o Joystick. Esto es hecho por simplicidad (y por velocidad). Llamando a la función setEventCallback (que hablaremos de ella más tarde) muchas veces resultará en que sólo el último listener registrado conseguirá los eventos. Si necesitas múltiples objetos para conseguir una tecla (Key), ratón (Mouse), o eventos de Joystick, tendrás que escribir un despachador de mensajes por tí mismo. También, estate seguro de notar que todavía llamas a Keyboard::capture and Mouse::capture en el método frameRenderingQueued. OIS no usa hilos (o magia) para determinar los estados del teclado o del ratón, así que tendrás que especificar cuando debería capturarlos.

La Interfaz KeyListener


La interfaz KeyListener de OIS proporciona dos funciones puras virtuales. La primera funcion es keyPressed (la cual es llamada cada vez que una tecla es presionada) y keyReleased (la cual es llamada cada vez que una tecla se deja levantada). El parametro pasado a estas funciones es un KeyEvent, que contiene el codigo de la tecla que esta siendo presionada/soltada.

La Interfaz MouseListener


La interfaz MouseListener es sólo un poco más compleja que la interfaz KeyListener. Contiene funciones para mirar cuando un botón del ratón fue presionado o soltado: MouseListener::mousePressed y MouseListener::mouseReleased. También contiene una función mouseMoved, que es llamada cuando el ratón se mueve. Cada una de estas funciones recibe un objeto MouseEvent, que contiene el estado actual del ratón en la variable "state".
La cosa más importante a notar sobre el objeto MouseState es que contiene no sólo las coordenadas X e Y del movimiento del ratón (que es, lo lejos que se ha movido desde la última vez que la función MouseListener::mouseMoved fue llamada), pero también las coordenadas absolutas X e Y (que es, donde estan exactamente en la pantalla).

El Código


Variables


Unas pocas variables han cambiado desde el último tutorial. He eliminado mToggle y mMouseDown (ya que no son necesarias). Y he añadido unas pocas:
Real mRotate; // La constante de rotacion
Real mMove; // La constante de movimiento
 
SceneNode *mCamNode; // El SceneNode de la camara que esta actualmente acoplada
 
Vector3 mDirection; // Valor para moverse en la direccion correcta
mRotate, mMove y mCamNode son los mismo desde el último tutorial (pienso que cambiaremos el valor de mRotate ya que estamos usándolo de forma diferente. la variable mDirection contiene información sobre como traducir el nodo de cámara en cada frame.

Método createFrameListener


En este método llamamos al metodo createFrameListener desde BaseApplication e inicializamos algunas variables. Añadir el siguiente código a tu método createFrameListener:
// Contenedor de la camara
mCamNode = mCamera->getParentSceneNode();
 
 // Establecer la rotacion y la velocidad de movimiento
mRotate = 0.13;
mMove = 250;
Los objetos OIS mMouse y mKeyboard son obtenidos en el método createFrameListener desde BaseApplication. Y allí registramos el listener llamando al método setEventCallback sobre estos objetos de entrada como sigue:
mMouse->setEventCallback(this);
mKeyboard->setEventCallback(this);
Por último, necesitamos inicializar mDirection para que sea el vector zero (ya que lo estamos inicializando sin movimiento):
mDirection = Ogre::Vector3::ZERO;

Enlazando las Teclas


Antes de seguir, deberías enlazar la tecla Escape para salir del programa para que podamos ejecutarlo. Busca el método BasicTutorial05::keyPressed. Este método es llamado con un objeto KeyEvent cada vez que un botón se presionó en el teclado. Podemos obtener el código de la tecla (KC_*) que fue presionada comprobando la variable "key" del objeto. Construiremos un interruptor para todas las teclas enlazadas que usamos en la aplicación basada en este valor. Busca el método keyPressed y reemplázalo con el siguiente código:
bool keyPressed(const OIS::KeyEvent& evt)
{
 switch (evt.key)
 {
 case OIS::KC_ESCAPE:
 mShutDown = true;
 break;
 default:
 break;
 }
 return true;
}
También tenemos que añadir el siguiente código al método frameRenderingQueued para que el programa responda a la entrada de teclado:
if (mWindow->isClosed()) return false;
 if (mShutDown) return false;
 mKeyboard->capture();
 mMouse->capture();
 mTrayMgr->frameRenderingQueued(evt);
Esto asegura que el programa sale cuando mShutDown es true (cámbialo con escape como se vió en el código anterior) si es presionado cuando la ventana se cierra.
mKeyboard->capture() y mMouse->capture aseguran que ambos el teclado y el mouse son capturado por nuestra aplicación. Finalmente el mTrayMgr->frameRenderingQueued(evt) hace que nuestra bandeja ui se renderice correctamente.

Asegúrate de que puedes compilar y ejecutar la aplicación antes de continuar.

Ahora necesitas añadir los enlaces para las otras teclas en esa sentencia de interruptor. La primera cosa que tenemos que hacer es permitir cambiar entre los viewports presionando 1 y 2. El código para esto (necesita ser incluido en la sentencia del interruptor) es la misma que la anterior del tutorial previo, excepto en que no tendrá que tratar con la variable mToggle:
case OIS::KC_1:
 mCamera->getParentSceneNode()->detachObject(mCamera);
 mCamNode = mSceneMgr->getSceneNode("CamNode1");
 mCamNode->attachObject(mCamera);
 break;
 
case OIS::KC_2:
 mCamera->getParentSceneNode()->detachObject(mCamera);
 mCamNode = mSceneMgr->getSceneNode("CamNode2");
 mCamNode->attachObject(mCamera);
 break;

Como puedes ver, esto es mucho más claro que tratar con una variable temporal siguiéndole la pista de los tiempos de cambio.
La siguiente cosa que tenemos que hacer es añadir este movimiento de teclado. Cada vez que el usuario presiona una tecla que es el límite del movimiento, añadimos o substraemos mMove (dependiendo de la dirección) desde la dirección correcta en el vector:

case OIS::KC_UP:
case OIS::KC_W:
mDirection.z = -mMove;
break;
 
case OIS::KC_DOWN:
case OIS::KC_S:
mDirection.z = mMove;
break;
 
case OIS::KC_LEFT:
case OIS::KC_A:
mDirection.x = -mMove;
break;
 
case OIS::KC_RIGHT:
case OIS::KC_D:
mDirection.x = mMove;
break;
 
case OIS::KC_PGDOWN:
case OIS::KC_E:
mDirection.y = -mMove;
break;
 
case OIS::KC_PGUP:
case OIS::KC_Q:
mDirection.y = mMove;
break;
Ahora necesitamos hacer "undo" (deshacer) para cambiar el vector mDirection si la tecla se suelta para parar el movimiento. Busca el método keyReleased y añádele este código:
switch (evt.key)
{
case OIS::KC_UP:
case OIS::KC_W:
mDirection.z = 0;
break;
 
case OIS::KC_DOWN:
case OIS::KC_S:
mDirection.z = 0;
break;
 
case OIS::KC_LEFT:
case OIS::KC_A:
mDirection.x = 0;
break;
 
case OIS::KC_RIGHT:
case OIS::KC_D:
mDirection.x = 0;
break;
 
case OIS::KC_PGDOWN:
case OIS::KC_E:
mDirection.y = 0;
break;
 
case OIS::KC_PGUP:
case OIS::KC_Q:
mDirection.y = 0;
break;
default:
break;
}
return true;
Ahora que hemos actualizado mDirection basándonos en la entrada de las teclas, necesitamos que la translación ocurra. Este código es el mismo que el del último tutorial excepto que movemos el nodo de la cámara en vez del ninja, así añadimos esto a la función frameRenderingQueued:
mCamNode->translate(mDirection * evt.timeSinceLastFrame, Ogre::Node::TS_LOCAL);
Compila y ejecuta la aplicación. Ahora tenemos un movimiento basado en teclas usando la entrada con búfer!

Enlazando el Ratón

Ahora que tenemos en enlazado de las teclas completado, necesitamos trabajar para conseguir que el ratón funcione. Deberiamos comenzar con el cambiador de la luz para encendido y apagado basado en el click del botón izquierdo del ratón. Busca la función mousePressed y échale un vistazo a los parámetros. Con OIS, podemos acceder a ambos, a MouseEven al igual que MouseButtonID. Podemos cambiar a encendido sobre el MouseButtonID para determinar el botón que fue presionado. Reemplaza el código en la función mousePressed con lo siguiente:
Ogre::Light *light = mSceneMgr->getLight("Light1");
switch (id)
{
case OIS::MB_Left:
light->setVisible(! light->isVisible());
break;
default:
break;
}
return true;
Compila y ejecuta la aplicación. Ahora que esta funcionando, la única cosa que nos falta es enlazar el botón derecho del ratón. Cada vez que el ratón se mueva, comprobamos para ver si el botón derecho del ratón se ha presionado. Si es asi, rotamos la cámara basándonos en el movimiento relativo. Podemos acceder al movimiento relativo del ratón desde el objeto MouseEvent pasado dentro de esta función. Que contiene una variable llamada "state" (estado) que contiene el MouseState (lo que es básicamente información detallada del ratón). El MouseState::buttonDown nos dirá si un botón del ratón se mantiene presionado, y las variables "X" e "Y" nos diran el movimiento de ratón relativo. Busca la función mouseMoved y reemplaza el código de ella con lo siguiente:
if (evt.state.buttonDown(OIS::MB_Right))
{
mCamNode->yaw(Ogre::Degree(-mRotate * evt.state.X.rel), Ogre::Node::TS_WORLD);
mCamNode->pitch(Ogre::Degree(-mRotate * evt.state.Y.rel), Ogre::Node::TS_LOCAL);
}
return true;
Compila y ejecuta la aplicación y la cámara actuará en un modo de vista libre mientras el botón derecho del ratón se mantenga presionado.

Otros sistemas de entrada


OIS es por lo general muy bueno, y debería ser usado para la mayoría de nuestras aplicaciones. Con esto queremos decir, que hay otras alternativas si deseas hacer cosas diferentes. Algunos sistemas de ventanas serían lo que estas buscando, como por ejemplo wxWidgets, que la gente ha conseguido integrar con Ogre exitosamente.
Puedes usar el sistema de mensajes de Windows o uno de los muchos kits de GUI de Linux, si no te importa que tu aplicación sea para una plataforma específica.

Puedes intentar también SDL, que proporciona no sólo sistemas multiplataforma de ventanas/entrada, también entrada de joystick/gamepad. No te puedo dar una guía para ajustes en wx/gtk/qt/etc con Ogre (nunca los he usado), he usado con éxito la entrada de SDL joystick/gamepad. Para conseguir que el sistema de joystick SDL comience, envuelve el inicio de tu aplicación con llamadas a SDL_Init y SDL_Quit (esto puede estar en la función principal de tu aplicación, o posiblemente en tu objeto aplicación):
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE);
SDL_JoystickEventState(SDL_ENABLE);
 
app.go();
 
SDL_Quit();
Para instalar el Joystick, llama a SDL_JoystickOpen con el número de Joystick (puedes especificar múltiples llamándolos con 0, 1, 2...):
SDL_Joystick* mJoystick;
mJoystick = SDL_JoystickOpen(0);
if ( mJoystick == NULL )
; // error handling
Si SDL_JoystickOpen devuelve NULL, entonces hubo un problema abriendo el joystick. Lo que siempre significa que el joystick que has pedido no existe. Usa SDL_NumJoysticks para encontrar cuantos joysticks estan acoplados al sistema. Tambien necesitas cerrar el joystick después de que hayas hecho lo que quieras:
SDL_JoystickClose(mJoystick);
Usa el joystick, llama a los botones SDL_JoystickGetButton y SDL_JoystickGetAxis. Usé esto con un controlador de playstation2 asi que tenia cuatro ejes y 12 botones para jugar. Este es mi código de movimiento:
SDL_JoystickUpdate();
mTrans.z += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 1) / 32767;
mTrans.x += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 0) / 32767;
 
xRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 3) / 32767;
yRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 2) / 32767;
mTrans fue consumido posteriormente dentro del método SceneNode::translate de la cámara, xRot fue consumido dentro de SceneNode::yaw, y yRot fue consumido dentro de SceneNode::pitch. Nota que el SDL_JoystickGetAxis devuelve un valor entre -32767 y 32767, asi que lo he escalado para que este entre -1 y 1. Esto debería hacer que comiences a usar la entrada de joystick SDL. Si estas buscando más información sobre este área, la mejor documentación esta en la cabecera del joystick SDL.

Deberías también referirte a la documentación estándar SDL si comienzas a usarlo seriamente en tu aplicación.

Siguiente Tutorial Básico 6 La Secuencia de Comienzo de Ogre