<< Voltar Índice Próxima >>

FRAMELISTENER E INPUT

FrameListener

A classe FrameListener é responsável pelo loop da Ogre, ela possui dois métodos:

bool frameStarted(const FrameEvent& evt)
bool frameEnded(const FrameEvent& evt)

O primeiro é o "frameStarted()" que é chamado antes da renderização do frame na janela e "frameEnded()" que é chamado após a renderização. O código do seu projeto pode ser colocado todo em um deles ou distribuído entre os dois.

O loop principal da Ogre é executado da seguinte forma:

1º - O objeto root chama o método "frameStarted()" de todos os FrameListeners registrados.
2º - O objeto root renderiza o frame, imagem na tela.
3º - O objeto root chama o método "frameEnded()" de todos os FrameListeners registrados.

Esse loop continua até que um dos métodos retorne falso(false), caso isso aconteça o programa para imediatamente. Dessa forma se algo em seu jogo necessitar estar no loop principal, ele deve estar em um desses métodos. Na Ogre é possível existir mais de um FrameListener no programa, mas não existe uma forma de determinar qual deles será executado primeiro, não existe um controle, por isso é mais aconselhável trabalhar com somente um FrameListener.

class AplicationListener : public FrameListener, public OIS::KeyListener, 
public OIS::MouseListener, public OIS::JoyStickListener

 

O trecho de código acima está no arquivo "AplicationListener.h", pois a classe do nosso projeto que contém o loop principal do jogo é a AplicationListener, com isso ela deve herdar a classe FrameListener para utilizarmos os métodos "frameStarted()" e "frameEnded()". As outras classes que ela também herda serão explicadas mais a frente.

aplicationListener = new AplicationListener(root, window, camera, scene_mgr);
root->addFrameListener(aplicationListener);
root->startRendering();

 

Para registrarmos um FrameListener devemos adicioná-lo ao nó root, como no trecho acima, que está no método "Vai()" da classe Inicializa.

Inicialmente criamos o objeto do tipo AplicationListener e passamos como parâmetros as váriaveis root, windows, camera e scene_mgr(SceneManager), pois elas serão necessárias para manipulações e configurações de elementos da Ogre. Por exemplo, se caso tivermos que atualizar a posição do robô no ambiente 3D, então teremos que utilizar o SceneManager para acessá-lo e mudar sua posição. Depois disso registramos o objeto da classe AplicationListener ao nó root com a função "addFrameListener()" e só assim podemos iniciar o loop principal da Ogre com a função "startRendering()".

AplicationListener::AplicationListener(Root *rootP, RenderWindow *winP, Camera *camP, 
   SceneManager *sceneManP)
{
   root = rootP;
   window = winP;
   camera = camP;
   scene_mgr = sceneManP;

 

O construtor da classe AplicationListener utilizamos para inicializar as variáveis usadas no programa. De início os parâmetros do construtor são passados para as váriaveis da nossa classe, que estão declaradas no arquivo "AplicationListener.h", fazemos isso para que possamos utilizar o root, window, camera e scene_mgr(SceneManager) em toda a classe e não somente no construtor.

   moveCam = 500;
   rotateCam = 0.5;
   walkSpeed = 200;
   direcao = Vector3::ZERO;
   direcaoCam = Vector3::ZERO;

 

A váriavel moveCam determina a velocidade de movimentação da câmera, rotateCam a velocidade de rotação da câmera, walkSpeed define a velocidade em que o robô irá andar, "direcao" a direção de movimentação do robô e direcaoCam a direção de movimentação da câmera.

   //adiciona camera tipo FPS
   camNode = scene_mgr->getRootSceneNode()->createChildSceneNode("cameraNode");
   camNode->attachObject(camera);
 
   continua = true;

 

Inicialmente a câmera irá se comportar parecida com a de jogos estilo FPS(tiro em primeira pessoa), como Counter-Strike e Quake. No método "Configura()" criamos a câmera e adicionamos um viewport para ela, mas agora devemos anexar ela a um SceneNode para que possamo manipular ela. Criamos um SceneNode camNode e depois anexamos a "camera". A váriavel continua irá determinar se a Ogre deverá continuar a execução ou não, então definimos ela inicialmente como true.

Input

A OIS(Open Input System) é uma biblioteca que possibilita o uso de teclado, mouse e joystick a qual é utilizada na Ogre. Voltando para o arquivo AplicationListener.h onde está a declaração da classe que contém o loop do jogo, encontramos as outras três classes que a AplicationListener extende que são as necessarias para a manipulação do teclado(KeyListener), mouse(MouseListener) e joysticks(JoyStickListener) através da OIS..

class AplicationListener : public FrameListener, public OIS::KeyListener, 
public OIS::MouseListener, public OIS::JoyStickListener

 

Com o objetivo de facilitar a configuração da OIS é utilizada a classe InputManager, que já está incluida nos arquivo do projeto IRADOSogreP3.

inputMgr = InputManager::getSingletonPtr();
inputMgr->initialise(window);
inputMgr->addKeyListener(this,"IraKeylistener");
inputMgr->addMouseListener(this,"IraMouselistner");
inputMgr->addJoystickListener(this,"IraJoysticklistener");

 

Inicialmente é criado o objeto inputMgr do tipo InputManager que será utilizado para o uso da OIS. Para inicializar a OIS utilizamos a função "initialise()" e passamos como parâmetro a janela que estamos utilizando para mostrar a nossa cena.

Depois adicionamos os listeners do teclado, mouse e joystick respectivamente. O primeiro parâmetro é a classe a qual conterá os métodos de manipulação dos inputs, no caso a classe que estamos adicionando o código, portanto usamos "this". O segundo parâmetro se refere ao nome que será dado ao listener para um futuro acesso, assim como ocorre com Entitys e SceneNodes.

forward = left = right = back = up = down = turnL = turnR = false;
mouseEsqDown = mouseDirDown = false;

 

As variáveis listadas acima são utilizadas para determinar em que direção o robô ou a câmera(dependendo do estilo de jogo,) está se movimentando, um exemplo de câmera estilo FPS e MMORPG serão abordados no próxima parte, por enquanto iremos locomover somente a câmera. Estas variáveis são atualizadas nos métodos que cuidam dos eventos do teclado e mouse. Caso a câmera esteja se movimentando para a frente a variável forward ficará como true, caso contrário false.

InputManager::getSingletonPtr()->capture();

 

A linha acima está no método "frameStarted()", é nela que é realizada a captura das entradas do usuário.

transVector = Vector3::ZERO;
if(forward) transVector.z -= moveCam;
if(back) transVector.z += moveCam;
if(left) transVector.x -= moveCam;
if(right) transVector.x += moveCam;
if(up) transVector.y += moveCam;
if(down) transVector.y -=moveCam;

 

No trecho de código acima o vetor transVector, que irá guardar o valor das mudanças que serão feitas na posição da câmera para que ela se movimente, é inicializado. A partir daí o vetor é atualizado de acordo com as variáveis de controle, que são tratadas no método "keyReleased()", visto mais a frente. Se a variável back por exemplo, estiver como verdadeira o vetor tem o seu valor no eixo Z somado ao valor de moveCam, que guarda a quantidade de movimentação da câmera.

camNode->translate(camNode->getOrientation() * transVector * ((float)evt.timeSinceLastFrame));

 

Com isso é utilizada a função "translate()" para mover a câmera. O parâmetro é um vetor que é o resultado da multiplicação do vetor de orientação da câmera( getOrientation() ), ou seja, para onde está voltada a frente da câmera, do vetor transVector e do tempo passado desde o último frame renderizado, que é guardado na variável evt.timeSinceLastFrame.

Uma observação importante é que como evt.timeSinceLastFrame está no método "frameStarted()" ela guarda o tempo passado desde a última vez que o método "frameStarted()" foi executado, ou seja, desde o último frame renderizado. Isso também vale para a função "frameEnded()".

A manipulação dos comandos do jogador pelo teclado é feito pelos métodos "keyPressed()" que é chamado quando uma tecla é pressionada e por "keyReleased()" executado quando uma tecla é solta.

bool AplicationListener::keyPressed( const OIS::KeyEvent &e ) {

	switch(e.key)
	{
	case OIS::KC_W:
		forward = true;
		break;
	case OIS::KC_A:
		left = true;
		break;
	case OIS::KC_D:
		right = true;
		break;
	case OIS::KC_S:
		back = true;
		break;
	case OIS::KC_PGUP:
	case OIS::KC_Q:
		up = true;
		break;
	case OIS::KC_PGDOWN:
	case OIS::KC_E:
		down = true;
		break; 
	}

    return true;
}

 

O método "keyPressed()" é mostrado acima, nele é usado o comando e.key para se obter a tecla pressionada, e utilizando um switch, compara-se com um dos códigos de tecla da OIS para se determinar qual ação deve ser executada. No nosso código ele atualiza os valores das variáveis de controle. A OIS possui mapeamento para todos botões do teclado.

As entradas do mouse são manipuladas por "mouseMoved()" chamado quando o mouse é movimentado, "mousePressed()" e "mouseReleased()" são invocados quando algum dos botões do mouse for pressionado e liberado respectivamente.

bool AplicationListener::mouseMoved( const OIS::MouseEvent &e ){
	
	if(e.state.X.abs>500){
		scene_mgr->getSceneNode("roboNode")->setScale(Vector3(3,3,3));
	}

	return true;
}

 

Pode se obter o quanto o mouse se movimentou em um dos eixos da tela, usa-se o comando e.state.X.abs. No exemplo acima o robô ficará 3 vezes maior caso o mouse se movimente de uma só vez mais de 500 unidades de medida no eixo dos X, ou seja, direita ou esquerda.

bool AplicationListener::mouseReleased( const OIS::MouseEvent &e, OIS::MouseButtonID id ) 
{
    if (id == OIS::MB_Left)
    {
        onLeftReleased(e);
        mouseEsqDown = false;
    } // if

    else if (id == OIS::MB_Right)
	{	
        onRightReleased(e);
        mouseDirDown = false;
    } // else if

	return true;
}

 

Os métodos "mousePressed()" e "mouseReleased()" atualizam as variáveis de verificação de acionamento dos botões do mouse. Eles foram dividos em botão direito "onRightPressed()" e esquerdo "onLeftPressed()" para uma melhor organização do código. O parâmetro id retorna qual dos botões do mouse foi pressionado, o botão do meio também é aceito pela OIS.

A OIS oferece suporte a entradas vindas joysticks(controles) de video games convencionais, como por exemplo do playstation. Os métodos "buttonPressed()" e "buttonReleased()" são executados quando um dos botões do joystick é pressionado e solto respectivamente.

bool AplicationListener::buttonReleased(const OIS::JoyStickEvent &evt, int index) {

	if(evt.device->getID() == 0){
		if(index == 1){
			scene_mgr->getSceneNode("roboNode")->setScale(Vector3(2,2,2));
		}
	}	

	return true;
}

 

No método "buttonReleased()" para se obter qual controle teve seu botão liberado utiliza-se o comando evt.device->getID(), lembrando que o primeiro controle tem ID 0(zero), o segundo ID 1(um) e assim sucessivamente. Depois de se conhecer qual o joystick foi acionado, podemos obter o botão solto apartir da váriavel index, que é o segundo parâmetro da função. Essa váriavel contém um valor inteiro que determinar o número do botão.

O primeiro botão tem valor zero e o maior valor é a quantidade total de botões do controle. Infelizmente não existe uma regra para se conhecer os índices de cada um dos botões, a não ser colocando um valor a ser comparado com index e testando no joystick qual dos botões corresponde a ele. Pode-se utilizar o código acima para realizar esses testes. O que acontecerá no código acima é que se no controle 0(zero), quando o botão 1(um) for solto o robô ficará com o dobro do tamanho( ->setScale(Vector(2,2,2)) ).

bool AplicationListener::axisMoved(const OIS::JoyStickEvent &evt, int index) {
	
	if(evt.device->getID() == 1){
		if(evt.state.mAxes[index].abs > 0 ){
			scene_mgr->getSceneNode("roboNode")->setScale(Vector3(3,3,3));
		}
	}

	return true;
}

 

Com a função "axisMoved()" manipula-se as entradas vindas do direcional(axis) do joystick. O comando para se obter o controle acionado é o mesmo dos métodos anteriores, e para se conhecer qual a direção pressionada utilizamos o comando:
evt.state.mAxes[index].abs

Sendo que o parâmetro index da função, agora contém qual dos eixos(X ou Y) do direcional foi usado. O valor do eixo Y é 0(zero) e do X é 1(um). A direção é determinada fazendo uma comparação com 0, a figura abaixo resume essa comparação.

                            mAxes[0].abs < 0
                                    ^
                                    |
                                    |
         mAxes[1].abs < 0   < - - - + - - - >   mAxes[1].abs > 0
                                    |
                                    |
                                    v
                            mAxes[0].abs > 0
void AplicationListener::windowResized(RenderWindow* rw) 
{
	// atualiza o tamanho da janela para a OIS
	inputMgr->setWindowExtents(rw->getWidth(), rw->getHeight());
}

 

Finalizando a parte de Inputs não se deve esquecer do método "windowResized()" que é chamado sempre que a janela de execução do jogo tem seu tamanho alterado, nela devemos inserir o comando:
inputMgr->setWindowExtents(rw->getWidth(), rw->getHeight());

Afim de que OIS possa atualizar suas configurações internas, para que não se tenha inconsistência ou erro dos valores obtidos.

Italo Mendes
italo.ribeiro@gmail.com

<< Voltar Índice Próxima >>