Table of Contents
LAB4: Human Interface Devices
This lab is only losely 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
Discussion thread for this lab is here: Lab 4 Discussion Thread
Goal
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).
Preparation
No special preparation is required - preparing the joypad is explained in the project steps below.
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!
Lab Project
Follow these steps to complete the lab project:
- Create a New Project Create a new empty project called “Lab4” in the same way you have created new projectsfor other lab projects.
- Move Character with Arrow Keys In the hand-out from Lab 1 you should now complete the then optional step 10: Controlling the model with the arrow keys. NOTE: Be aware that you have already made some changes to your code since Lab 1, you need to preserve those. For example, continue to pass your ogre entity into the MyFrameListener constructor, even if you now also have to pass your ogre node into the same constructor.
- Adding Animation Continue with the hand-out from Lab 1 and now complete the then optional step 11: Adding Animation. Again make sure you preserve your prior code.
- Plug in and Test Joypad Plug a USB joypad into your computer and find the device in your system's control panel (usually under “Devices and Printers”). Examine the device properties and find a small test panel for the device. Make sure the device works. Intall necessary drivers if the system is not picking it up.
- Initialize Joystick Input Your MyFrameListener class needs to receive callbacks from the joystick device and therefore has to inherit joystick callback methods from an abstract class called
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 ) { return true; } bool buttonPressed( const OIS::JoyStickEvent &arg, int button ) { return true; }; bool buttonReleased( const OIS::JoyStickEvent &arg, int button ) { return true; }; bool 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.
- Move Character with POV Now you want to give players the option to move the character by pressing one of the directions on the POV (point of view) digital button on the left of the joypad. You should still retain the keyboard control of the movement. First explore the values you get when you press the POV button. Do that by adding the following two lines to the
povMoved
callback:int direction = arg.state.mPOV[pov].direction; std::cout << direction << "\n";
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 code:
_WalkingNorth = false; _WalkingSouth = false; _WalkingEast = false; _WalkingWest = false; switch(direction) { case OIS::Pov::North: _WalkingSouth = true; break; case OIS::Pov::South: _WalkingNorth = true; break; case OIS::Pov::East: _WalkingWest = true; break; case OIS::Pov::West: _WalkingEast = true; break; case OIS::Pov::Centered: break; }
Make sure also to declare each of the
bool
variables as member variables in your class! This is a bit verbose here for clarity, but you can also pack these bits into a single byte to save space and use bit masking. Notice that the POV directions don't necessarily map correctly onto the directions in our world. You can now use these booleans just like the keyboard button presses for moving the character in your class'sframeStarted
method. In fact, you can use the OR operator to check to see if a keyboard direction was pressed OR the corresponding walking direction is true, for example:if(_Keyboard->isKeyDown(OIS::KC_UP) || _WalkingNorth) { ... }
Finally, you need to capture the joystick input in this
frameStarted
method:if( _Joystick ) _Joystick->capture();
- Rotate Camera with Analog Stick Now you should try to rotate (or swivel) the camera around the character with the right analog stick on the joypad. You first need to create a new member variable in your MyFrameListener class that stores the current camera angle (e.g.
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 youraxisMoved
callback:int value = e.state.mAxes[axis].abs; switch(axis) { case 0: std::cout << "0:" << 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 theframeStarted
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.
- OPTIONAL: Map more axes and buttons Add more controls to the camera or other things in the environment. For example, zoom the camera in and out with a different axis of movement or let a button start the effect from lab 2.
When You Are Finished
Upload your commented source files into Lab4 in MySchool (zip them up if more than one). The lab projects will not be graded, but their completion counts towards your participation grade.