This lab is only loosely based on Chapter 8 on human interface devices in the text book. The focus here is on being able to control the on-screen character with a specialized game interface device.
Discussion thread for this lab is here on Piazza: https://piazza.com/class/ij2xbg3ztb41qt?cid=31
The primary goal of this lab is to hook a joypad game input device up to your Ogre application and use it to control both the character and the camera. This is also an opportunity to see how you set up buffered input (previously you've been using unbuffered input in this application).
No special preparation is required - preparing the joypad is explained in the project steps below. You do not have to use the latest lab as a base for this one, you can use the base of lab2 or any other except lab1 if you feel like the project has become to bloated.
NOTE: To get the joypads to start sending data, you may have to press a Mode button in the center of the device. This is true for the joypads handed out in the lab class!
Follow these steps to complete the lab project:
OIS::JoyStickListener
. Luckily you can inherit from multiple classes! Change your declaration of your MyFrameListener into this: class MyFrameListener : public Ogre::FrameListener, public OIS::JoyStickListener {
You then have to provide implementations of a few abstract callback methods:
bool axisMoved(const OIS::JoyStickEvent &e, int axis); bool buttonPressed(const OIS::JoyStickEvent &arg, int button); bool buttonReleased(const OIS::JoyStickEvent &arg, int button); bool povMoved(const OIS::JoyStickEvent &arg, int pov); bool MyFrameListener::axisMoved(const OIS::JoyStickEvent &e, int axis) { return true; } bool MyFrameListener::buttonPressed(const OIS::JoyStickEvent &arg, int button) { return true; } bool MyFrameListener::buttonReleased(const OIS::JoyStickEvent &arg, int button) { return true; } bool MyFrameListener::povMoved(const OIS::JoyStickEvent &arg, int pov) { return true; }
In this class you need a new member variable to hold a pointer to a new joystick input object (e.g. OIS::JoyStick* _Joystick;
) and then you have to create this object in your constructor. Because it is not guaranteed that the joypad is connected, you need to catch a possible exception upon creation and let the application gracefully continue even if the creation fails:
try { _Joystick = static_cast<OIS::JoyStick*>(_InputManager->createInputObject( OIS::OISJoyStick, true )); _Joystick->setEventCallback(this); std::cout << "Successfuly created Joystick"; } catch(...) { std::cout << "Failed to initialize Joystick"; _Joystick = 0; }
Don't forget to also destroy the joystick object in your destructor, just like you do with the mouse and keyboard. Make sure this code compiles and check to see if your joypad gets properly initialized.
// Create a member variable in the MyFrameListener class: int _povState bool MyFrameListener::povMoved(const OIS::JoyStickEvent &arg, int pov) { _povState = arg.state.mPOV[pov].direction; std::cout << _povState << "\n"; return true; }
Notice that you get special codes for each direction and the code 0 when you let go of the button (it goes back into center position). Unlike the keyboard, you don't get continuous events when you press and hold the directions! To use this for moving the character you need to store the last direction code received and reset it when you receive 0. In the same callback (below the two lines above), add the following OR
operation to your keyboard movement if statements:
if (_Keyboard->isKeyDown(OIS::KC_UP) || _povState == OIS::Pov::North) { SinbadTranslate += Ogre::Vector3(0, 0, -1); walked = true; _rotation = 3.14f; } if (_Keyboard->isKeyDown(OIS::KC_DOWN) || _povState == OIS::Pov::South) { SinbadTranslate += Ogre::Vector3(0, 0, 1); walked = true; _rotation = 0.0f; } if (_Keyboard->isKeyDown(OIS::KC_LEFT) || _povState == OIS::Pov::West) { SinbadTranslate += Ogre::Vector3(-1, 0, 0); walked = true; _rotation = -1.57f; } if (_Keyboard->isKeyDown(OIS::KC_RIGHT) || _povState == OIS::Pov::East) { SinbadTranslate += Ogre::Vector3(1, 0, 0); walked = true; _rotation = 1.57f; }
Finally, you need to capture the joystick input in this frameStarted
method:
if( _Joystick ) _Joystick->capture();
float _camangle
). In your constructor, initialize this angle to the value -1*Ogre::Math::HALF_PI
so that this angle corresponds to the initial orientation of the character. As with the POV button, you should examine the values you get from the analog axes of your joypad. Add the following lines to your axisMoved
callback:int value = e.state.mAxes[axis].abs; switch(axis) { case 1: std::cout << "1:" << value << "\n"; break; }
You can add more cases to this to observe values along the other axes. You will notice that they each run from a negative 32768 to a positive 32767, which corresponds to the range of a 16-bit signed integer. For one of these axes, we want this range to map onto a camera angle that runs from 0 to 360 degrees (calculated in radians). The camera angle can therefore be calculated as follows:
_camangle = Ogre::Math::TWO_PI * ((float(-1*value) / float(_Joystick->MAX_AXIS) + 1.0f)/2.0f)+Ogre::Math::HALF_PI;
The -1
in here is to reverse the axis value to let the direction of movement map better onto the perceived camera motion and the HALF_PI offset is just so that we start by facing the ogre model correctly. Finally, in the frameStarted
callback you have to continue to update the camera position and orientation:
_Cam->setPosition(_myogrenode->getPosition()+Ogre::Vector3(20.0f*Ogre::Math::Cos(_camangle), 10.0f, 20.0f*Ogre::Math::Sin(_camangle))); _Cam->lookAt(_myogrenode->getPosition());
Test to see if this works.
Bonus points
Ogre::Vector3 SinbadTranslate(0, 0, 0); float _rotation = 0.0f; if (_Keyboard->isKeyDown(OIS::KC_UP) || _povState == OIS::Pov::North) { SinbadTranslate += Ogre::Vector3(0, 0, -1); walked = true; _rotation = 3.14f; } if (_Keyboard->isKeyDown(OIS::KC_DOWN) || _povState == OIS::Pov::South) { SinbadTranslate += Ogre::Vector3(0, 0, 1); walked = true; _rotation = 0.0f; } if (_Keyboard->isKeyDown(OIS::KC_LEFT) || _povState == OIS::Pov::West) { SinbadTranslate += Ogre::Vector3(-1, 0, 0); walked = true; _rotation = -1.57f; } if (_Keyboard->isKeyDown(OIS::KC_RIGHT) || _povState == OIS::Pov::East) { SinbadTranslate += Ogre::Vector3(1, 0, 0); walked = true; _rotation = 1.57f; } _node->translate(SinbadTranslate * evt.timeSinceLastFrame * _WalkingSpeed); _node->resetOrientation(); _node->yaw(Ogre::Radian(_rotation));
MyFrameListener
class, they will hold the values for the left analog axis. And make sure to initialize them all to 0. int _walkMagnitude; int _turnMagnitude; float _orientation;
axisMoved
by adding to the previously created switch statement. bool MyFrameListener::axisMoved(const OIS::JoyStickEvent &e, int axis) { int value = e.state.mAxes[axis].abs; switch (axis) { case 1: _camangle = Ogre::Math::TWO_PI * ((float(-1 * value) / float(_Joystick->MAX_AXIS) + 1.0f) / 2.0f) + Ogre::Math::HALF_PI; break; // Add these lines, dont forget to break :) case 2: _walkMagnitude = (float)value / -float(_Joystick->MAX_AXIS); // Map the range to -1 to 1 break; case 3: _turnMagnitude = (float)value / -float(_Joystick->MAX_AXIS); // Map the range to -1 to 1 break; } return true; }
bool walked = false; Ogre::Vector3 SinbadTranslate(0, 0, 0); // If the joystick is not available, use the keyboard as input. if (!_Joystick) { if (_Keyboard->isKeyDown(OIS::KC_UP)) { _walkMagnitude = 1; } else if (_Keyboard->isKeyDown(OIS::KC_DOWN)) { _walkMagnitude = -1; } else { _walkMagnitude = 0; } if (_Keyboard->isKeyDown(OIS::KC_LEFT)) { _turnMagnitude = 1; } else if (_Keyboard->isKeyDown(OIS::KC_RIGHT)) { _turnMagnitude = -1; } else { _turnMagnitude = 0; } } // Create the translation vector. SinbadTranslate = _node->getOrientation().zAxis() * evt.timeSinceLastFrame * _WalkingSpeed * _walkMagnitude; walked = true; // Increment the roation angle. _orientation += evt.timeSinceLastFrame * _turnMagnitude*2; // Now finally apply the rotation and translation. _node->translate(SinbadTranslate); _node->setOrientation(Ogre::Quaternion(Ogre::Radian(_orientation), Ogre::Vector3::UNIT_Y));
// This will make the camera a child of the Sinbad Scene node, and thus all transformation made to Sinbad will be applied to the Camera as well. _node->attachObject(_Cam);
Buffered keyboard input, Make the following changes to the MyFrameListener class to enable the buffered keyboard input
// Add the inheritance of OIS::KeyListener, this will be required to make your FrameListener handle // the buffered input by providing you with the two virtual functions, keyPressed and keyReleased. class MyFrameListener : public Ogre::FrameListener, OIS::JoyStickListener, OIS::KeyListener { //... // Change the way the frameListener initializes the Keyboard object pointer in the constructor. // And make "MyFrameListener" the event callback Handler for the keyboard. MyFrameListener() : ... { //... // Notice the last parameter in the createInputObject() has changed from false to true!! // That parameter indicates that we want to enable buffered keyboard input. _Keyboard = static_cast<OIS::Keyboard*>(_InputManager->createInputObject(OIS::OISKeyboard, true)); _Keyboard->setEventCallback(this); //... } // Now implement those two virtual functions. // The KeyEvent holds information on which key was pressed. virtual bool keyPressed(const OIS::KeyEvent &arg) { return true; } virtual bool keyReleased(const OIS::KeyEvent &arg) { return true; } //... }
Now you have those two functions that trigger only once per key press, and are not continuous like before, did I mention buffered input :)?
Upload your source files into Lab5 in MySchool (zip them up if more than one). The lab projects will not be graded, but their completion counts towards your participation grade.