Basic Tutorial 5         Buffered Input
Print

Tutorial Introduction
Image In this short tutorial you will be learning to use OIS's buffered input as opposed to the unbuffered input we used last tutorial. This tutorial differs from the last in that we will be handling keyboard and mouse events immediately, as they happen, instead of once per frame. Note that this is only meant as an introduction to buffered input, and not a complete tutorial on how to use OIS. For more information on that, be sure to look at the Using OIS article.

 

Any problems you encounter during working with this tutorial should be posted in the Help Forum(external link).

 

Prerequisites

  • This tutorial assumes you have knowledge of C++ programming and are able to set up and compile an Ogre application.
  • This tutorial also assumes that you have created a project using the Ogre Wiki Tutorial Framework, either manually, using CMake or the Ogre AppWizard - see Setting Up An Application for instructions.
  • This tutorial builds on the previous basic tutorials, and it assumes you have already worked through them.

 
You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.

Getting Started

 
This tutorial will be building on the last tutorial, but we are changing the way that we do input. Since the functionality will be basically the same, we will use the same BaseApplication class from last time. Create a project in the compiler of your choice for this project, and add the following code:

BasicTutorial05 header
#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;          // The rotate constant
    Ogre::Real mMove;            // The movement constant
    Ogre::SceneNode *mCamNode;   // The SceneNode the camera is currently attached to
    Ogre::Vector3 mDirection;    // Value to move in the correct direction
 
};
 
#endif // #ifndef __BasicTutorial05_h_

 

BasicTutorial05 implementation
#include "BasicTutorial05.h"
 
//-------------------------------------------------------------------------------------
BasicTutorial05::BasicTutorial05(void)
{
}
//-------------------------------------------------------------------------------------
BasicTutorial05::~BasicTutorial05(void)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial05::createScene(void)
{
	mSceneMgr->setAmbientLight(Ogre::ColourValue(0.25, 0.25, 0.25));
 
        // add the ninja
	Ogre::Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");
        Ogre::SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
        node->attachObject(ent);
 
        // create the light
        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);
 
       // Create the scene node
        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Ogre::Vector3(-400, 200, 400));
 
        // Make it look towards the ninja
        node->yaw(Ogre::Degree(-45));
 
        // Create the pitch node
        node = node->createChildSceneNode("PitchNode1");
        node->attachObject(mCamera);
 
        // create the second camera node/pitch node
        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
    {
        // Create application object
        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

 
The program controls will be the same as in the last tutorial. We will override the frameRenderingQueued and createFrameListener methods, as well as the MouseListener and KeyboardListener event methods (keyPressed(), keyReleased(), mouseMoved(), mousePressed() and mouseReleased).

Buffered Input in a Nutshell

 

Introduction

 
In the previous tutorial we used unbuffered input, that is, every frame we queried the state of OIS::Keyboard and OIS::Mouse instances to see what keys and mouse buttons were being held down. Buffered input uses listener interfaces to inform your program that events have occurred. For example, when a key is pressed, a KeyListener::keyPressed event is fired and when the button is released (no longer being pressed) a KeyListener::keyReleased event is fired to all registered KeyListener classes. This takes care of having to keep track of toggle times or whether the key was unpressed the previous frame.
OIS also supports unbuffered Joystick events through the OIS::JoystickListener interface, though we will not cover how to use this in this tutorial.

One important thing to note about OIS's listener system is that you can only have one listener per Keyboard, Mouse, or Joystick object. This is done for simplicity (and for speed). Calling the setEventCallback function (which we will cover later) multiple times will result in only the last registered listener getting events. If you need multiple objects to get Key, Mouse, or Joystick events, you will have to write a message dispatch yourself. Also, be sure to note that we still call the Keyboard::capture and Mouse::capture in the frameRenderingQueued method. OIS does not use threads (or magic) to determine the keyboard and mouse states, so you will have to specify when it should capture the input.

 

The KeyListener Interface

 
OIS's KeyListener interface provides two pure virtual functions. The first is the keyPressed function (which is called every time a key is pressed) and the second is keyReleased (which is called every time a key is let up). The parameter passed to these functions is a KeyEvent, which contains the key code of what is being pressed/released.

The MouseListener Interface

 
The MouseListener interface is only slightly more complex than the KeyListener interface. It contains two functions to see when a mouse button is pressed or released: MouseListener::mousePressed and MouseListener::mouseReleased. It also contains a mouseMoved function, which is called when the mouse is moved. Each of these functions receives a MouseEvent object, which contains the current state of the mouse in the "state" variable. The most important thing to note about the MouseState object is that it contains not only the relative X and Y coordinates of the mouse's last movement (that is, how far it has moved since the last time the MouseListener::mouseMoved function was called), but also the absolute X and Y coordinates (that is, where exactly on the screen they are).

The Code

 

Variables

 
A few variables have changed from the last tutorial. I have removed mToggle and mMouseDown (which are no longer needed). I have added a few as well:

Real mRotate;          // The rotate constant
Real mMove;            // The movement constant
 
SceneNode *mCamNode;   // The SceneNode the camera is currently attached to
 
Vector3 mDirection;     // Value to move in the correct direction

 
mRotate, mMove and mCamNode are the same as in the last tutorial, though we will be changing the value of mRotate since we are using it differently. The mDirection variable contains information on how we are going to translate the camera node every frame.

createFrameListener method

 
In this method we will call the createFrameListener method from BaseApplication and initialize some of the variables. Add the following code to your createFrameListener method:

// Populate the camera container
mCamNode = mCamera->getParentSceneNode();
 
// set the rotation and move speed
mRotate = 0.13;
mMove = 250;

 
The OIS mMouse and mKeyboard objects are already obtained in the createFrameListener method from BaseApplication. In there we register the listener by calling the setEventCallback method on these input objects as follows:

// Do not add this code to the project!
mMouse->setEventCallback(this);
mKeyboard->setEventCallback(this);

 
Last, we need to initialise mDirection to be the zero vector (since we are initially not moving):

mDirection = Ogre::Vector3::ZERO;

 

Key Bindings

 
Before we go any further, we should bind the Escape key to exiting the program so we can run it. Find the BasicTutorial05::keyPressed method. This method is called with a KeyEvent object every time a button on the keyboard goes down. We can obtain the key code (KC_*) of the key that was pressed by checking the "key" variable on the object. We will build a switch for all of the key bindings we use in the application based on this value. Find the keyPressed method and replace it with the following code:

bool keyPressed(const OIS::KeyEvent& evt)
{
    switch (evt.key)
    {
    case OIS::KC_ESCAPE: 
        mShutDown = true;
        break;
    default:
        break;
    }
    return true;
}

 
We also need to add the following code to the frameRenderingQueued method for the program to respond to keyboard input:

if (mWindow->isClosed()) return false;
    if (mShutDown) return false;
    mKeyboard->capture();
    mMouse->capture();
    mTrayMgr->frameRenderingQueued(evt);

 
This makes sure the program exits when mShutDown is true (toggled by the Esc key as seen in the code above) and when the window is closed. mKeyboard->capture() and mMouse->capture() make sure that both keyboard and mouse events are captured by our application. Finally, mTrayMgr->frameRenderingQueued(evt) makes sure our tray UI is rendered correctly.

Make sure you can compile and run the application before continuing.

Now we need to add bindings for other keys in that switch statement. The first thing we are going to do is allow the user to switch between the viewpoints by pressing 1 and 2. The code for this (needs to be included in the switch statement) is the same as it was in the previous tutorial, except we no longer have to deal with the mToggle variable:

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;

 
As you can see, this is much cleaner than dealing with a temporary variable to keep track of toggle times.

The next thing we are going to add is keyboard movement. Every time the user presses a key that is bound for movement, we will add or subtract mMove (depending on the direction) from the correct direction in the 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;

 
Now we need to "undo" the change to the mDirection vector whenever the key is released to stop the movement. Find the keyReleased method and add this code to it:

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;

 
Now that we have mDirection updated based on key input, we need to actually make the translation happen. This code is the same as the last tutorial except we move the camera node instead of the ninja, so add this to the frameRenderingQueued function:

mCamNode->translate(mDirection * evt.timeSinceLastFrame, Ogre::Node::TS_LOCAL);

 
Compile and run the application. We now have key-based movement using buffered input!

Mouse Bindings

 
Now that we have key bindings completed, we need to work on getting the mouse working. We'll start with toggling the light on and off based on a left mouse click. Find the mousePressed function and take a look at the parameters. With OIS, we have access to both a MouseEvent as well as a MouseButtonID. We can switch on the MouseButtonID to determine the button that was pressed. Replace the code in the mousePressed function with the following:

Ogre::Light *light = mSceneMgr->getLight("Light1");
switch (id)
{
case OIS::MB_Left:
    light->setVisible(! light->isVisible());
    break;
default:
    break;
}
return true;

 
Compile and run the application. Voilà ! Now that this is working, the only thing left to do is to bind the right mouse button to a mouse look mode. Every time the mouse is moved, we will check to see if the right mouse button is down. If it is, we will rotate the camera based on the relative movement. We can access the relative movement of the mouse from the MouseEvent object passed into this function. It contains a variable called "state" which contains the MouseState (which is basically detailed information about the mouse). The MouseState::buttonDown will tell us whether or not a particular mouse button is held down, and the "X" and "Y" variables will tell us the relative mouse movement. Find the mouseMoved function and replace the code in it with the following:

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;

 
Compile and run the application and the camera will act in free-look mode while the right mouse button is held down.

Other Input Systems

 
OIS is generally very good, and should suit most purposes for your application. With that said, there are alternatives if you wish to use something different. Some windowing systems may be what you are looking for, such as wxWidgets(external link), which people have successfully integrated with Ogre. You can use the standard Windows message system or one of the many Linux GUI toolkits for input, if you don't mind your application being platform specific.

You can also try SDL, which provides not only cross-platform windowing/input systems, but also joystick/gamepad input. While I cannot give you guidance on setting up WX, GTK, Qt, etc. with Ogre (as I've never done it), I have had a good amount of success getting SDL's joystick/gamepad input to work well with Ogre. To get SDL's joystick system started, wrap your application's intial startup with SDL_Init and SDL_Quit calls (this can be in your application's main function, or possibly in your application object):

SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE);
SDL_JoystickEventState(SDL_ENABLE);
 
app.go();
 
SDL_Quit();

To setup the Joystick, call SDL_JoystickOpen with the Joystick number (you can specify multiple joysticks by calling with 0, 1, 2...):

SDL_Joystick* mJoystick;
mJoystick = SDL_JoystickOpen(0);
 
if ( mJoystick == NULL )
    ; // error handling

If SDL_JoystickOpen returns NULL, then there was a problem opening the joystick. This almost always means that the joystick you requested doesn't exist. Use SDL_NumJoysticks to find out how many joysticks are attached to the system. You also need to close the joystick after you are done with it:

SDL_JoystickClose(mJoystick);

To use the joystick, call the SDL_JoystickGetButton and SDL_JoystickGetAxis buttons. I personally used this with a playstation2 controller(external link), so I had four axes to play with and twelve buttons to play with. This is my movement code:

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 was later fed into the camera's SceneNode::translate method, xRot was fed into SceneNode::yaw, and yRot was fed into SceneNode::pitch. Note that the SDL_JoystickGetAxis returns a value between -32767 and 32767, so I have scaled it to be between -1 and 1. This should get you started using SDL joystick input. If you are looking for more information in this area, the best documentation comes from the SDL joystick header(external link).

You should also refer to the standard SDL documentation if you start to seriously use this in your application.

Proceed to Basic Tutorial 6 The Ogre Startup Sequence


Alias: Basic_Tutorial_5


Contributors to this page: Gwynbleidd1 points  , Rubinsson236 points  , peters181 points  , JustBoo , JacobM1 points  , jacmoe111451 points  , Hrhon91 points  , Harkathmaker181 points  , davidgraig247 points  , cherishlove57 points  , Beauty5965 points  and atomr181 points  .
Page last modified on Thursday 29 of December, 2011 11:25:54 GMT by Gwynbleidd1 points .


The content on this page is licensed under the terms of the Creative Commons Attribution-ShareAlike License.
As an exception, any source code contributed within the content is released into the Public Domain.