Skip to main content

History: Basic Tutorial 5

Source of version: 54

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="2294" 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. We will go over the details of the new input code shortly.
{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);

  virtual bool keyPressed(const OIS::KeyEvent& ke);
  virtual bool keyReleased(const OIS::KeyEvent& ke);

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

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

  if (mShutdown) return false;
  if (mWindow->isClosed()) return false;

  return ret;
}

bool TutorialApplication::keyPressed(const OIS::KeyEvent& ke)
{
  switch (ke.key)
  {
  case OIS::KC_ESCAPE:
    mShutdown = true;
    break;

  default:
    break;
  }

  return true;
}

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

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

// MAIN FUNCTION OMITTED FOR SPACE
{CODE}

Compile and run your application. You should see the tudor house in front of you. You won't be able to move, but you should be able to exit the application if you press escape.
!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 MouseListener Interfaces
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.

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.
!Setting Up SceneNodes For Camera Positioning
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:

{CODE(wrap="1", colors="c++")}
// Populate the camera container
mCamNode = mCamera->getParentSceneNode();
 
// set the rotation and move speed
mRotate = 0.13;
mMove = 250;
{CODE}

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: 

{CODE(wrap="1", colors="c++")}
// Do not add this code to the project!
mMouse->setEventCallback(this);
mKeyboard->setEventCallback(this);
{CODE}

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

{CODE(wrap="1", colors="c++")}
mDirection = Ogre::Vector3::ZERO;
{CODE}

!!!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: 

{CODE(wrap="1", colors="c++")}
bool keyPressed(const OIS::KeyEvent& evt)
{
    switch (evt.key)
    {
    case OIS::KC_ESCAPE: 
        mShutDown = true;
        break;
    default:
        break;
    }
    return true;
}
{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