Skip to main content

History: Basic Tutorial 5

Source of version: 71

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 void createFrameListener();
  virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);

  bool mShutdown;
  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()
  : mShutdown(false),
    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);

}

void TutorialApplication::createFrameListener()
{
  BaseApplication::createFrameListener();
}

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 no__ 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. 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. Luckily, 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(caption="BaseApplication::createFrameListener" wrap="1", colors="c++")}
mMouse->setEventCallback(this);
mKeyboard->setEventCallback(this);
{CODE}
This registers our class (which inherits from KeyListener and MouseListener) as the source of the callback methods. As long as we continue to call the BaseApplication method, then these listeners will be registered correctly.
!Dealing With Input Events
Take a look at the our current {MONO()}TutorialApplication::keyPressed{MONO} method:
{CODE(wrap="1", colors="c++")}
bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke)
{
  switch (ke.key)
  {
  case OIS::KC_ESCAPE: 
      mShutDown = true;
      break;
  default:
      break;
  }
    
  return true;
}
{CODE}
When this callback method is called, the KeyEvent reference that is passed in will contain information about which key was pressed. This is stored in the {MONO()}key{MONO} member of the KeyEvent. We have set up a switch statement that will search through a number of different possible keys to see if something should occur because of the event. In our case, we already have a check for when the escape key is pressed. You can see that the OIS namespace defines a series of "key codes" for all of the keys on the keyboard. Currently, we simply set our flag for shutting down our application to be true when the escape key is pressed. We will soon add more functionality.


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: 


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

{CODE (wrap="1", colors="c++")}
        if (mWindow->isClosed()) return false;
    if (mShutDown) return false;
    mKeyboard->capture();
    mMouse->capture();
    mTrayMgr->frameRenderingQueued(evt);
{CODE}

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. {MONO()}mKeyboard->capture(){MONO} and {MONO()}mMouse->capture(){MONO} make sure that both keyboard and mouse events are captured by our application. Finally, {MONO()}mTrayMgr->frameRenderingQueued(evt){MONO} 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: 

{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}

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: 

{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}

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:

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

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: 

{CODE(wrap="1", colors="c++")}
mCamNode->translate(mDirection * evt.timeSinceLastFrame, Ogre::Node::TS_LOCAL);
{CODE}

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: 

{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