Tutorial de sistema de camara en 3ra persona Como crear un sistema de camara, basico y flexible.


El Fuente es para Ogre Eihort(1.4)

La idea


La idea para este sistema de camara vino mientras estaba mirando el como se hizo de Silent Hill 2, donde algunas pantallas de depuracion aparecen y voy a notar algunas "lineas". Si no lo has visto, comprenderas de lo que estoy hablando.

Bien. La idea principal detras de este sistema de camara es tener solo una camara (normalmente) en la escena al mismo tiempo. como oposicion a otros sistemas de camara de 3ra/1ra persona, aqui la camara es "desacoplada" desde la escena entera (excepto por el nodo raiz - que es crucial para que funciones ;)).

Esta filosofia nos permite obtener muy buenos efectos y movimiento suave, dando un modo amigable de ver la escena.

En terminos generales, el nucleo de camara (aqui llamado ExtendedCamera) consiste de dos nodos de escena, que actuaran como el "manejador" de camara (handler) y el objetivo de camara (target camera). El manejador se supone que esta mirando siempre al objetivo. Moviendo el objetivo resultara en que se mire alrededor; Moviendo el "manejador", pivotando, rolling...
alrededor del objetivo; moviendo ambos al mismo tiempo y cantidad...

Esta es una version simple del sistema. Las mejoras avanzadas nos permitirian efectos de agitado, camaras cinematicas (crane, rail...) a traves de camaras Virtuales.

Te explicare como estos tres modos diferentes de camara incluidos en esta demo funcionan en la camara de tercera persona.

Camara en 3ra persona - Perseguir


Tenemos un personaje principal, que tiene un nodo principal (el actor), un modo ciego (el punto de vista del personaje se supone esta mirando hacia alla), y un nodo de camara (donde pensamos que la camara estaria mejor emplazada). hay otros modos de conseguir el mismo efecto, pero usaremos esto por simplicidad.

Lo que haremos es usar el modo ciego como la posicion deseada del objetivo de la camara, y el nodo de la camara como la posicion de la camara en si mismo.

Mas alla de los nodos ciegos el personaje se desplazara desde el centro de la pantalla (por ejemplo, si el personaje esta en el modo "investigacion" en el juego de camara en 3ra persona, en oposicion al modo "andar alrededor"). Esto ayuda a mantener una vista hacia delante de la escena, mientra mantiene la pista del personaje.

Camara en 3ra persona - Fija


Esta clase de camara puede ser vista en muchos juegos de aventura como Resident Evil, Silent Hill, Alone in the Dark... La idea es que el objetivo siga la vista del personaje, pero se mantendra fijo en una unica posicion.

El modo en que esta camara funciona es similar a la chasing de camara.

Variaciones de esto son las camaras crane, camaras rail... usadas en conjuntos de pelicula.

Camara en 1ra persona


En vez de usar el chase deseado en la posicion de la camara, usamos la posicion del personaje como la posicion deseada de la camara. Para este modo, las camaras con un valor de tightness de 1 funcionan mejor (y ocultar el modelo del personaje tambien!).
Esta es la unica diferencia con las camaras en 3ra persona descritan antes.

Cosas importantes a tener en la mente


Sistemas de Coordenadas

Como la camara es independiente de cualquier objeto en la escena, trabajaremos con coordenadas de mundo.

Movimientos de Camara


Los movimientos de Camara (y el objetivo) en este modo. Calculamos la diferencia entre la posicion de la camara y la posicion deseada. Como resultados obtenemos un vector desplazamiento que sera aplicado a la camara para que se mueva a la posicion deseada.

Tightness "Opresión"

Como el movimiento descrito antes es demasiado rigido para ser "agradable para el usuario", necesitamos encontrar un modo de hacerlo mas suave. Asi aparece el concepto de movimiento tightness. El factor de tightness va del 0.0 a 1.0, y determina que proporcion de vector desplazamiento se aplicara.

Un factor de Tightness de 1.0 resulta en un movimiento rigido: Cada unidad de objeto mueve, la camara tambien.
Un factor de Tightness de 0.0 resulta en que no hay movimiento: El vector desplazamiento tendra una longitud de 0, asi que ningun movimiento se realizara.
Los factores de Tightness que salen del 0.0-1.0 resultan en resultados no deseados.

El codigo fuente, explicado


Puedes encontrar este codigo fuente portado a OgreDotNet por Alberts? aqui (o acoplado a esta pagina):
external image zip.pngExtendedCameraSample.zip

Puedes encontrar este tutorial y codigo fuente portado a Python-Ogre por Zyzle?
Aqui.

Lo primero de todo, tenemos que incluir una cabecera para usar el framework de ejemplo de OGRE

/* Tutorial del sistema de camara por Kencho */
 
#include "ExampleApplication.h"

Siguiente, definimos una clase generica Character (Personaje). Aqui, la clase Character es usada para definir cada objeto que puede ser seguido y chased por una camara. Claro, en un juego o aplicacion, esto deberia tener muchos miembros ;)

// clase Generica Character "Personaje"
class Character {
// Atributos ------------------------------------------------------------------------------
 
   protected:
      SceneNode *mMainNode; // Nodo principal del personaje
      SceneNode *mSightNode; // Nodo Ciego - El personaje se supone que esta mirando hacia alli
      SceneNode *mCameraNode; // Nodo para el chase de la camara
      Entity *mEntity; Entidad Caracter
      SceneManager *mSceneMgr;
   public:
// Metodos ---------------------------------------------------------------------------------
 
   protected:
   public:
      // Actualizaciones del personaje (movimiento...)
      virtual void update (Real elapsedTime, OIS::Keyboard *input) = 0;
      // Tres metodos debajo que devuelven los dos nodos relativos de camara,
      // y la actual posicion del personaje (para la camara en primera persona)
      SceneNode *getSightNode () {
          return mSightNode;
      }
      SceneNode *getCameraNode () {
          return mCameraNode;
      }
      Vector3 getWorldPosition () {
          return mMainNode->_getDerivedPosition ();
      }
};


Siguiente: Nuestra especializacion de la clase Character para esta demo. Pienso una bonita cabeza de Ogre flotante es un personaje perfecto para esta demostracion, asi que definimos una especializacion de la clase Character, que manejara, sus nodos, modelo, y movimiento de tipo 3D (girar a la izquierda/ a la derecha,...)

// Especializacion de la clase Character - Nuestro querido Ogro :D
class OgreCharacter : public Character {
// Atributos ------------------------------------------------------------------------------
 
protected:
    String mName;
public:
// Metodos ---------------------------------------------------------------------------------
protected:
public:
   OgreCharacter (String name, SceneManager *sceneMgr) {
       // Establecer la referencia basica a los miembros
       mName = name;
       mSceneMgr = sceneMgr;
 
       // Establece la estructura basica del nodo para manejar las camaras en tercera persona
       mMainNode = mSceneMgr->getRootSceneNode ()->createChildSceneNode (mName);
       mSightNode = mMainNode->createChildSceneNode (mName + "_sight", Vector3 (0, 0, 100));
       mCameraNode = mMainNode->createChildSceneNode (mName + "_camera", Vector3 (0, 50, -100));
 
       // Dar a este personaje una forma :)
       mEntity = mSceneMgr->createEntity (mName, "OgreHead.mesh");
       mMainNode->attachObject (mEntity);
      }
      ~OgreCharacter () {
          mMainNode->detachAllObjects ();
          delete mEntity;
          mMainNode->removeAndDestroyAllChildren ();
          mSceneMgr->destroySceneNode (mName);
      }
 
      void update (Real elapsedTime, OIS::Keyboard *input) {
          // Handle movement
          if (input->isKeyDown (OIS::KC_W)) {
              mMainNode->translate (mMainNode->getOrientation () * Vector3 (0, 0, 100 * elapsedTime));
          }
          if (input->isKeyDown (OIS::KC_S)) {
              mMainNode->translate (mMainNode->getOrientation () * Vector3 (0, 0, -50 * elapsedTime));
          }
          if (input->isKeyDown (OIS::KC_A)) {
              mMainNode->yaw (Radian (2 * elapsedTime));
          }
          if (input->isKeyDown (OIS::KC_D)) {
              mMainNode->yaw (Radian (-2 * elapsedTime));
          }
       }
 
       // Cambia la visibilidad - Util para la vista en 1ra persona ;)
       void setVisible (bool visible) {
           mMainNode->setVisible (visible);
       }
};


Para mantener la simplicidad, he evitado los metodos para cambiar la vision del personaje, animar el modelo...

Ahora, la parte interesante: La clase ExtendedCamera. Sigue la filosofia que describi antes. El codigo es auto explicativo, asi que si tienes preguntas sobre como funciona, puedes leer otra vez las secciones anteriores :->

// Nuestra clase de camara extendida
class ExtendedCamera {
// Atributos ------------------------------------------------------------------------------
   protected:
       SceneNode *mTargetNode; // El objetivo de la camara
       SceneNode *mCameraNode; // La camara en si misma
       Camera *mCamera; // La camara del Ogro
 
       // SceneManager *mSceneMgr;
       String mName;
 
       bool mOwnCamera; // Para saber si la camara del ogro ha sido enlazada creandola fuera o dentro de esta clase.
 
       Real mTightness; // Determina el movimiento de la camara - 1 significa movimiento tight, mientras 0 significa sin movimiento
public:
// Metodos ---------------------------------------------------------------------------------
   protected:
   public:
        ExtendedCamera (String name, SceneManager *sceneMgr, Camera *camera = 0) {
        // Instalacion de referencias de miembros basicas
        mName = name;
        mSceneMgr = sceneMgr;
 
        // Crear la estructura del nodo de la camara
        mCameraNode = mSceneMgr->getRootSceneNode ()->createChildSceneNode (mName);
        mTargetNode = mSceneMgr->getRootSceneNode ()->createChildSceneNode (mName + "_target");
        mCameraNode->setAutoTracking (true, mTargetNode); // La camara siempre mira al objetivo de la camara
        mCameraNode->setFixedYawAxis (true); // Necesitado por el auto seguimiento.
 
        // Crea nuestra camara si no fue pasada como un parametro
        if (camera == 0) {
            mCamera = mSceneMgr->createCamera (mName);
            mOwnCamera = true;
        }
        else {
            mCamera = camera;
            // solo se asegura de que mCamera se establezca a "origin" (origen) (la misma posicion que el mCameraNode)
            mCamera->setPosition(0.0,0.0,0.0);
            mOwnCamera = false;
        }
        // ... y acopla la camara de Ogre al nodo de la camara
        mCameraNode->attachObject (mCamera);
 
        // tightness por defecto
        mTightness = 0.01f;
        }
        ~ExtendedCamera () {
             mCameraNode->detachAllObjects ();
             if (mOwnCamera)
                  delete mCamera;
             mSceneMgr->destroySceneNode (mName);
             mSceneMgr->destroySceneNode (mName + "_target");
        }
 
        void setTightness (Real tightness) {
             mTightness = tightness;
        }
 
        Real getTightness () {
            return mTightness;
        }
 
        Vector3 getCameraPosition () {
            return mCameraNode->getPosition ();
        }
 
        void instantUpdate (Vector3 cameraPosition, Vector3 targetPosition) {
            mCameraNode->setPosition (cameraPosition);
            mTargetNode->setPosition (targetPosition);
        }
 
        void update (Real elapsedTime, Vector3 cameraPosition, Vector3 targetPosition) {
            // Manejo del movimiento
            Vector3 displacement;
 
            displacement = (cameraPosition - mCameraNode->getPosition ()) * mTightness;
            mCameraNode->translate (displacement);
            displacement = (targetPosition - mTargetNode->getPosition ()) * mTightness;
            mTargetNode->translate (displacement);
        }
};


Un ejemplo de frame listener que manejara la actualizacion del personaje y la camara, y los cambios del modo de camara. Otra vez, el codigo es autoexplicativo y la filosofia se explico antes.


class SampleListener : public ExampleFrameListener
{
protected:
     // Referencias al personaje principal y a la camara
     Character *mChar;
     ExtendedCamera *mExCamera;
 
     // El modo de Camara - Ahora soporta la 1ra persona, la 3ra persona (chasing) y la 3ra persona (fija)
     unsigned int mMode;
 
public:
     SampleListener(RenderWindow* win, Camera* cam)
     : ExampleFrameListener(win, cam)
     {
         mChar = 0;
         mExCamera = 0;
         mMode = 0;
     }
 
     void setCharacter (Character *character) {
         mChar = character;
     }
 
     void setExtendedCamera (ExtendedCamera *cam) {
         mExCamera = cam;
     }
 
     bool frameStarted(const FrameEvent& evt)
     {
         mKeyboard->capture();
 
         if (mChar) {
              mChar->update (evt.timeSinceLastFrame, mKeyboard);
 
              if (mExCamera) {
                   switch (mMode) {
                       case 0: // 3ra persona persigue
                          mExCamera->update (evt.timeSinceLastFrame,
                          mChar->getCameraNode ()->getWorldPosition (),
                          mChar->getSightNode ()->getWorldPosition ());
                          break;
                       case 1: // 3ra persona fija
                          mExCamera->update (evt.timeSinceLastFrame,
                          Vector3 (0, 200, 0),
                          mChar->getSightNode ()->getWorldPosition ());
                          break;
                       case 2: // 1ra persona
                          mExCamera->update (evt.timeSinceLastFrame, mChar->getWorldPosition (), mChar->getSightNode ()->getWorldPosition ());
                          break;
                    }
              }
         }
 
         // 3ra Persona - Camera Seguimiento
         if (mKeyboard->isKeyDown (OIS::KC_F1)) {
             mMode = 0;
             if (mChar)
                  static_cast<OgreCharacter *>(mChar)->setVisible (true);
             if (mExCamera) {
                if (mChar)
                    mExCamera->instantUpdate (mChar->getCameraNode ()->getWorldPosition (), mChar->getSightNode ()->getWorldPosition ());
                mExCamera->setTightness (0.01f);
             }
         }
         // 3ra Persona - Camara Fija
         if (mKeyboard->isKeyDown (OIS::KC_F2)) {
             mMode = 1;
             if (mChar)
                  static_cast<OgreCharacter *>(mChar)->setVisible (true);
             if (mExCamera) {
                if (mChar)
                   mExCamera->instantUpdate (Vector3 (0, 200, 0), mChar->getSightNode ()->getWorldPosition ());
                mExCamera->setTightness (0.01f);
             }
         }
         // 1ra Persona
         if (mKeyboard->isKeyDown (OIS::KC_F3)) {
             mMode = 2;
             if (mChar)
             static_cast<OgreCharacter *>(mChar)->setVisible (false);
             if (mExCamera) {
                 if (mChar)
                     mExCamera->instantUpdate (mChar->getWorldPosition (), mChar->getSightNode ()->getWorldPosition ());
                 mExCamera->setTightness (1.0f);
             }
         }
 
         // Sale si presionamos Esc
         if(mKeyboard->isKeyDown (OIS::KC_ESCAPE))
              return false;
 
         return true;
    }
};

Un ejemplo de aplicacion. si tienes dudas sobre esto, intenta leer otras secciones del wiki :-)

class SampleApplication : public ExampleApplication
{
protected:
public:
   SampleApplication()
   {
   }
   ~SampleApplication()
   {
   }
 
protected:
   // Solo sobreescribe el mandato de crear el metodo de escena
   void createScene(void)
   {
       // Establece la luz de ambiente
       mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
 
       // LUCES!!
       // Crea un punto de luz
       Light* l = mSceneMgr->createLight("MainLight");
       // Acepta los ajustes por defecto: punto de luz, blanco difuso, solo establece la posicion
       // NB Podria acoplar la luz a un Nodo de Escena si quisiera moverlo con el automaticamente
       // otros objetos, pero no
       l->setType(Light::LT_DIRECTIONAL);
       l->setDirection(-0.5, -0.5, 0);
 
       // CAMARA!!
       mCamera->setPosition (0, 0, 0); Necesario o la camara tendra un desplazamiento
 
       // ACCION!!!
       // Llenar la escena con algunos razors
       SceneNode *razorNode;
       Entity *razorEntity;
       for (unsigned int i = 0; i < 30; ++i) {
            razorNode = mSceneMgr->getRootSceneNode ()->createChildSceneNode (StringConverter::toString (i), Vector3 (Math::RangeRandom (-1000, 1000), 0, Math::RangeRandom (-1000, 1000)));
            razorEntity = mSceneMgr->createEntity (StringConverter::toString (i), "razor.mesh");
            razorNode->attachObject (razorEntity);
       }
 
       // Personaje principal
       OgreCharacter *ogre = new OgreCharacter ("Ogre 1", mSceneMgr);
       ExtendedCamera *exCamera = new ExtendedCamera ("Extended Camera", mSceneMgr, mCamera);
 
       // Frame listener para manejar ambos personaje y las actualizaciones de camara y los diferentes modos de camara.
       // Necesitas crearlo aqui como tenemos que cambiar algunos parametros, ademas evitando definirlo.
       mFrameListener = new SampleListener (mWindow, mCamera);
       static_cast<SampleListener *>(mFrameListener)->setCharacter (ogre);
       static_cast<SampleListener *>(mFrameListener)->setExtendedCamera (exCamera);
   }
   void destroyScene(void)
   {
   }
   void createFrameListener(void)
   {
       // Aqui es donde hemos instanciado nuestro propio frame listener
       // mFrameListener= new SampleListener(mWindow, mCamera);
       mRoot->addFrameListener(mFrameListener);
   }
};

Nota que en el metodo createFrameListener(), el frame listener no sera construido como hicimos en el metodo createScene(), asi alli no tendremos dos frame listeners. Esto es importante como el segundo (y usado), si pudieramos crearlo en createFrameListener(), no deberiamos configurarlo, y ademas deberiamos estropear la aplicacion.

Para hacerlo capaz de funcionara

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
 
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
   // Crea el objeto de aplicacion
   SampleApplication app;
 
   try {
      app.go();
   } catch( 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
        fprintf(stderr, "An exception has occured: %s\n", e.getFullDescription().c_str());
   #endif
}
 
return 0;
}

Unas palabras para terminar


Espero que encuentres esto util. Actualizare esto mas tarde, arreglare algunas explicaciones y metodos. Gracias por leer.
Kencho