Skip to main content

History: Basic Tutorial 5

Source of version: 90

Copy to clipboard
            {TRANSCLUDE(page="tutbox")}This tutorial will introduce the use of buffered input with ((OIS)). Instead of asking for input information every frame, we will have callback methods defined that are called every time an input event occurs.

The full source for this tutorial is ((BasicTutorial5SourceCurrent|here)).{TRANSCLUDE}
%tutorialhelp%
!Prerequsites
This tutorial assumes that you already know how to set up an Ogre project and compile it successfully. If you need help with this, then read ((Setting Up An Application)). This tutorial is also part of the ((Basic Tutorials)) series and knowledge from the previous tutorials will be assumed.

{img fileId="2296" rel="box[g]"}
{maketoc}
!Setting Up the Scene
There is a 'tudorhouse.mesh' included in the Samples directory with Ogre, but the texture no longer seems to be included. Here is the texture:

{ATTACH(id="219")}{ATTACH}

After adding the mesh and texture to your project, you need to make sure this material entry is in one of your material scripts:
{CODE(wrap="1" colors="cfg")}
material Examples/TudorHouse
{
	technique
	{
		pass
		{
			texture_unit
			{
				texture fw12b.jpg
				tex_address_mode clamp
			}
		}
	}
}
{CODE}
Finally, you'll need to set up a basic scene for this tutorial. Compile and run this code. You should see the tutor house in front of you.
{CODE(caption="TutorialApplication.h" wrap="1" colors="c++")}
#ifndef TUTORIALAPPLICATION_H
#define TUTORIALAPPLICATION_H

#include "BaseApplication.h"

class TutorialApplication : public BaseApplication
{
public:
  TutorialApplication();
  virtual ~TutorialApplication();

private:
  virtual void createScene();
  virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);

  Ogre::Real mRotate;
  Ogre::Real mMove;
  Ogre::SceneNode* mCamNode;
  Ogre::Vector3 mDirection;

};

#endif
{CODE}
{CODE(caption="TutorialApplication.cpp" wrap="1" colors="c++")}
#include "TutorialApplication.h"

TutorialApplication::TutorialApplication()
  : mRotate(.13),
    mMove(250),
    mCamNode(0),
    mDirection(Ogre::Vector3::ZERO)
{
}

TutorialApplication::~TutorialApplication()
{
}

void TutorialApplication::createScene()
{
  mSceneMgr->setAmbientLight(Ogre::ColourValue(.2, .2, .2));

  Ogre::Entity* tudorEntity = mSceneMgr->createEntity("tudorhouse.mesh");
  Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(
    "Node");
  node->attachObject(tudorEntity);

  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);

  mCamera->setPosition(0, -370, 1000);

}

bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
  bool ret = BaseApplication::frameRenderingQueued(fe);

  return ret;
}

// MAIN FUNCTION OMITTED FOR SPACE
{CODE}

!An Introduction to Buffered Input
In the previous tutorial, we used unbuffered input. For instance, every frame we checked the OIS::Keyboard instance to see if any keys were being held down. Thist tutorial will use ''buffered'' input. This approach involves registering a listener class to report input events directly. It is called buffered input because the events are fed into a buffer and then dispatched via the callback methods. Don't worry if this seems too abstract, we will give concrete examples soon that will help clarify things.

With buffered input we no longer have to devise a system for keeping track of whether a button was held down the previous frame. Instead, there are two separate input events that are fired: a key pressed event and a key released event. And each event carries information like what key was pressed or released.

It is important to know that OIS only allows ''one'' listener per Keyboard, Mouse, or Joystick object. This is done for performance reasons. If you try to register a second listener, then it will simply replace the first. If you need multiple objects to be notified of input events, then you should implement your own message dispatching system.

Finally, notice that we still call the {MONO()}capture{MONO} on {MONO()}mKeyboard{MONO} and {MONO()}mMouse{MONO}. Even with buffered input, OIS still needs to be told to capture the state of these devices.
!The KeyListener and Interface
The listener class provided by OIS for the keyboard is called [http://code.joyridelabs.de/ois_api/classOIS_1_1KeyListener.html|KeyListener]. It provides two pure virtual callback methods: {MONO()}keyPressed{MONO} and {MONO()}keyReleased{MONO}. Both of these functions take a single [http://code.joyridelabs.de/ois_api/classOIS_1_1MouseEvent.html|KeyEvent] as a parameter. This object contains information about which key is being used. 

You should add overrides of the KeyListener methods to your header and cpp file. Add the declaration to the private section.
{CODE(caption="TutorialApplication.h" wrap="1" colors="c++")}
virtual bool keyPressed(const OIS::KeyEvent& ke);
virtual bool keyReleased(const OIS::KeyEvent& ke);
{CODE}
{CODE(caption="TutorialApplication.cpp" wrap="1" colors="c++")}
bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke) 
{ 
  return true; 
}

bool TutorialApplication::keyReleased(const OIS::KeyEvent& ke) 
{ 
  return true; 
}
{CODE}

!The MouseListener Interface
The listener class that OIS provides for the mouse is called [http://code.joyridelabs.de/ois_api/classOIS_1_1MouseListener.html|MouseListener]. It is only slight more complex than the KeyListener. It contains two methods to check the status of mouse buttons: {MONO()}mousePressed{MONO} and {MONO()}mouseReleased{MONO}. It also has a third method called {MONO()}mouseMoved{MONO} that is called whenever it detects an event caused by moving the mouse. The [|MouseEvent] that is passed to these functions contains information about the relative movement of the mouse (how far it has moved since the last frame) and information about where the mouse is in absolute screen coordinates. You'll find situations where both pieces of information are useful.

Go ahead and add overrides for the MouseListener methods to your header and cpp file. Add the declaration to the private section as well. __Do not__ compile your application yet. We've taken over some input management from BaseApplication and you'll be stuck with no way to move around or exit. We'll fix that soon.
{CODE(caption="TutorialApplication.h" wrap="1" colors="c++")}
virtual bool mouseMoved(const OIS::MouseEvent& me);
virtual bool mousePressed(const OIS::MouseEvent& me, OIS::MouseButtonID id);
virtual bool mouseReleased(const OIS::MouseEvent& me, OIS::MouseButtonID id);
{CODE}
{CODE(caption="TutorialApplication.cpp" wrap="1" colors="c++")}
bool TutorialApplication::mouseMoved(const OIS::MouseEvent& me) 
{ 
  return true; 
}

bool TutorialApplication::mousePressed(
  const OIS::MouseEvent& me, OIS::MouseButtonID id) 
{ 
  return true; 
}

bool TutorialApplication::mouseReleased(
  const OIS::MouseEvent& me, OIS::MouseButtonID id) 
{ 
  return true; 
}
{CODE}

!Setting Up SceneNodes For Camera Positioning
Now we are going to create two SceneNodes that we can attach the Camera to. This will allow us to toggle back and forth between the two viewpoints using buffered input. The first thing you should do is __remove__ this line in {MONO()}createScene{MONO}:
{CODE(wrap="1", colors="c++")}
mCamera->setPosition(0, -370, 1000);
{CODE}
We're now positioning the camera by attaching it to a SceneNode so this was no longer needed. Now we will build these SceneNodes. Add the following to {MONO()}createScene{MONO} right after we create the Light:
{CODE(wrap="1", colors="c++")}
node = mSceneMgr->getRootSceneNode()->createChildSceneNode(
  "CamNode1", Ogre::Vector3(1200, -370, 0));
node->yaw(Ogre::Degree(90));
{CODE}
Here we have created a new SceneNode and reused the {MONO()}node{MONO} variable to rotate it 90 degrees around the y-axis. The next thing we will do is assign our {MONO()}mCamNode{MONO} pointer to our new SceneNode and attach our Camera to it. {MONO()}mCamNode{MONO} will be used as our current SceneNode. We will move the pointer to another SceneNode based on keyboard input.
{CODE(wrap="1", colors="c++")}
mCamNode = node;
node->attachObject(mCamera);
{CODE}
This will mean our Camera will now start out at the position of the SceneNode it was just attached to. Now we want to create another SceneNode that we can move the Camera to.
{CODE(wrap="1", colors="c++")}
node = mSceneMgr->getRootSceneNode()->createChildSceneNode(
  "CamNode2", Ogre::Vector3(-500, -370, 1000));
node->yaw(Ogre::Degree(-30));
{CODE}
Now we have to "anchors" that we can attach the Camera to. Remember that the Camera won't just get placed at the position of the SceneNode, it will also inherit any transformations like rotations that have been applied to the node.
!Registering the Listeners
As with most listener classes, we need to register the KeyListener and MouseListener with their respective objects for them to work correctly. This is something that is already being done for us in BaseApplication. If you look at {MONO()}BaseApplication::createFrameListener{MONO}, you will see these two calls:
{CODE(wrap="1", colors="c++")}
mMouse->setEventCallback(this);
mKeyboard->setEventCallback(this);
{CODE}
They register our class (which inherits from KeyListener and MouseListener) as the source of the callback methods. Since we aren't overriding {MONO()}BaseApplication::createFrameListener{MONO}, this registration still happens in BaseApplication.
!Capturing Input State
OIS does not automatically capture the state of the keyboard and mouse every frame. We need to explicitly call the {MONO()}capture{MONO} method to do this. This is another thing that is already being done by the tutorial framework. If you look at {MONO()}BaseApplication::frameRenderingQueued{MONO}, you will see these two lines:
{CODE (wrap="1", colors="c++")}
mKeyboard->capture();
mMouse->capture();
{CODE}
These two calls fill the "buffers" with input information. This is why this method is referred to as buffered input. While we're here, you should also notice the line that makes sure {MONO()}mShutDown{MONO} causes the application to exit:
{CODE (wrap="1", colors="c++")}
if (mShutdown) return false;
{CODE}
These are all things we would have to put in our own class if BaseApplication wasn't taking care of them for us. The ((Intermediate Tutorials)) no longer use the BaseApplication framework to help move you away from relying on code that is "behind the scenes". For now, just keep in mind that a lot of our functionality is still coming from BaseApplication. We are trying to point them out here so things start to seem less mysterious. Also keep in mind that if we override one of the methods from BaseApplication, then we must remember to call the parent method so we continue to get the functionality from BaseApplication.
!Dealing With Input Events
We are now ready to develop our actual input code. The first thing we will do is allow the user to exit the application by pressing the escape key. Add the following to {MONO()}TutorialApplication::keyPressed{MONO}:
{CODE(wrap="1" colors="c++")}
switch (ke.key)
{
case OIS::KC_ESCAPE: 
  mShutDown = true;
  break;
default:
  break;
}
{CODE}
When the {MONO()}keyPressed{MONO} method is called by the listener, the KeyEvent reference that we have stored in {MONO()}ke{MONO} will contain a member called {MONO()}key{MONO}. We can use its value to determine which key was pressed. We will use a switch statement that tests for the key code associated with the escape key. The OIS namespace defines a series of key code constants that all start with the prefix "KC_". When we discover the escape key has been pressed, then we simply set our shutdown flag to true. {MONO()}BaseApplication::frameRenderingQueued{MONO} will then return false next frame and the application will exit as we wanted.

If you compile your application now, you won't be able to move the Camera, but you should be able to exit by pressing escape.
!Changing Viewpoints
Next we are going to allow the user to jump between SceneNodes by pressing 1 or 2 on the keyboard. Add the following to the switch statement we just set up in {MONO()}keyPressed{MONO}:
{CODE(wrap="1", colors="c++")}
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;
{CODE}
This will detach the Camera and reattach it whenever these keys are pressed. Compile your application. You should be able to change viewpoints. The only other thing you can do is press escape to exit.
!Camera Movement
Now we will add Camera movement back into our application. We are going to do this by using {MONO()}mDirection{MONO} as a velocity vector. Whenever the user presses the W key we will set the velocity vector's z component to equal {MONO()}-mMove{MONO}. This is because the Camera's default orientation is facing in the negative direction down the z-axis. We will apply the same logic to all of the different directions the user could move. Add the following to the switch statement in {MONO()}keyPressed{MONO} that we've been working with:
{CODE(wrap="1", colors="c++")}
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; 
{CODE}
You might want to take a moment to convince yourself this aims the vector in the right directions. The next thing we need to do is return a component to zero if the key is released. Add the following to {MONO()}keyReleased{MONO}:
{CODE(wrap="1", colors="c++")}
switch (ke.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;
{CODE}
This switch is just like the one we were using in {MONO()}keyPressed{MONO}. All of this code has only built the velocity vector we needed. Now we have to translate the Camera's current SceneNode based on this vector so that the Camera actually moves. Add the following to {MONO()}frameRenderingQueued{MONO} right after we call the BaseApplication method: 
{CODE(wrap="1", colors="c++")}
mCamNode->translate(mDirection * fe.timeSinceLastFrame, Ogre::Node::TS_LOCAL);
{CODE}
You should recognize our use of the local transformation space from the past tutorial. This coordinate frame stays attached to our Camera, so the negative z-axis will always point forwards in this transformation space. This is crucial to our method. You should also notice the method we used to smooth out the Camera's movement by scaling it based on the time that has passed since the last frame. If any of this is unclear, then refer to ((Basic Tutorial 4)).

Compile and run your application. We can now move the Camera using the keyboard again, but we've done it all using the buffered input system. Try moving the Camera and then jumping to the other SceneNode and back again. Notice that you return to the place you moved the SceneNode. It does not "reset" because when we are actually translating the Camera by using the SceneNode it is attached to. The SceneNode is not just a stationary place-holder.
!Toggling a Light With Buffered Input
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: 

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

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: 

{CODE(wrap="1", colors="c++")}
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;
{CODE}

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 [http://www.wxwindows.org/|wxWidgets], 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):

{CODE(wrap="1", colors="c++")}
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE);
SDL_JoystickEventState(SDL_ENABLE);

app.go();

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

{CODE(wrap="1", colors="c++")}
SDL_Joystick* mJoystick;
mJoystick = SDL_JoystickOpen(0);
 
if ( mJoystick == NULL )
    ; // error handling
{CODE}
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:

{CODE(wrap="1", colors="c++")}
SDL_JoystickClose(mJoystick);
{CODE}
To use the joystick, call the SDL_JoystickGetButton and SDL_JoystickGetAxis buttons. I personally used this with a [http://www.toysnjoys.com/access_ps2/ps2_controller.jpg|playstation2 controller], so I had four axes to play with and twelve buttons to play with. This is my movement code:

{CODE(wrap="1", colors="c++")}
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;
{CODE}
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 [http://www.jikos.cz/~crag/private/dox/SDL__joystick_8h-source.html|header].

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

!Conclusion
Now you should have a basic understanding of buffered input using OIS, plus a little insight into using SDL for joysticks.

!Full Source
The full source for this tutorial is ((BasicTutorial5SourceCurrent|here)).

!Next
((Basic Tutorial 6))
---
Alias: (alias(Basic_Tutorial_5))

        

History

Information Version
Mon 26 of Mar, 2018 09:38 GMT-0000 paroj 113
Tue 07 of Apr, 2015 22:03 GMT-0000 kabbotta 112
Tue 07 of Apr, 2015 22:01 GMT-0000 kabbotta 111
Tue 07 of Apr, 2015 21:59 GMT-0000 kabbotta 110
Tue 07 of Apr, 2015 21:57 GMT-0000 kabbotta 109
Tue 07 of Apr, 2015 21:12 GMT-0000 kabbotta 108
Tue 07 of Apr, 2015 21:05 GMT-0000 kabbotta 107
Tue 07 of Apr, 2015 21:04 GMT-0000 kabbotta 106
Tue 07 of Apr, 2015 21:03 GMT-0000 kabbotta 105
Tue 07 of Apr, 2015 21:03 GMT-0000 kabbotta 104
Tue 07 of Apr, 2015 21:00 GMT-0000 kabbotta 103
Tue 07 of Apr, 2015 20:59 GMT-0000 kabbotta 102
Tue 07 of Apr, 2015 20:48 GMT-0000 kabbotta 101
Tue 07 of Apr, 2015 08:27 GMT-0000 kabbotta 100
Tue 07 of Apr, 2015 08:23 GMT-0000 kabbotta 99
Tue 07 of Apr, 2015 08:21 GMT-0000 kabbotta 98
Tue 07 of Apr, 2015 08:18 GMT-0000 kabbotta 97
Tue 07 of Apr, 2015 08:15 GMT-0000 kabbotta 96
Tue 07 of Apr, 2015 08:14 GMT-0000 kabbotta 95
Tue 07 of Apr, 2015 08:07 GMT-0000 kabbotta 94
Tue 07 of Apr, 2015 07:58 GMT-0000 kabbotta 93
Tue 07 of Apr, 2015 07:49 GMT-0000 kabbotta 92
Tue 07 of Apr, 2015 07:49 GMT-0000 kabbotta 91
Tue 07 of Apr, 2015 07:48 GMT-0000 kabbotta 90
Tue 07 of Apr, 2015 07:45 GMT-0000 kabbotta 89