Game Development Community

Bug - buildXInputEvent()

by William Todd Scott · in Torque Game Engine Advanced · 02/17/2007 (11:15 am) · 9 replies

Hi,

There is a bug in DInputManager::buildXInputEvent() that causes it to work incorrectly with GuiCanvas::processInputEvent().

Specifically, in processInputEvent() when processing a deviceType of XInputDeviceType, it checks the objType and then checks the objInst to execute the correct function. However, in buildXInputEvent both the objType and the objInst are set to the same thing.

Furthermore, when using a 360 Controller the objType and objInst values set in processInputEvent correspond to actual button instances(e.g. XI_A, XI_B, XI_Y) instead of the values in processInputEvent() which are KEY_BUTTON0, KEY_BUTTON1, etc.

I'm not sure of the correct way to fix this.

Should I modify buildXInputEvent to set objType and objInst properly? Will that mess anything up?

Also, can someone verify that the section in processInputEvent() works properly with joysticks/gamepads? If not, then the whole section needs to be replaced. If it does work then a new section needs to be added to support the 360 controller.

Thanks
Todd

#1
02/20/2007 (8:39 pm)
***small bump***

Hi,

I am going to fix this locally, and I was hoping a GG (or other knowledgeable) person might comment on what they think is the right way to fix it. That way I can post the fix here and we can all get some leverage out of the time spent.

Specifically, if anyone could comment on the following two issues which will drive how I fix the problem.

1) Can anyone confirm if the existing XInputDeviceType code in GuiCanvas::procssInputEvent() works with a joystick? If so, then I will just add in the appropriate code to also support a 360 controller.

2) Should buildXInputEvent be modified to provide different obType and objInst values.

Thanks!
Todd
#2
02/21/2007 (3:14 pm)
Todd,
processInputEvent should work fine with joysticks/gamepads. My experience is coming from my implementation of XInput, fyi, so some things may differ. You are correct, objType and objInst should not be set to those values, and AFAIK changing those values to be proper should not hose anything. objType should be like SI_BUTTON or any of the other values, and objInst should be one of the XI_A, XI_B, etc enum.

I am 99% sure that the canvas code has no issue with gamepad events. The 1% is not remembering if I had to hack anything. You should just be able to add another block similar to
else if(event->deviceType == MouseDeviceType && cursorON)
Only it would be 'XInputDeviceType' instead. Note that non 360 controllers still use DirectInput and will show up as "joystick" not "gamepad" and I think the reason is that DirectInput gamepads don't seem to support analog values.
#3
02/21/2007 (4:13 pm)
Pat,

Thank you for the insight. This is exactly what I needed to know....

The following code already exists in GuiCanvas::processInputEvent():

else if( event->deviceType == XInputDeviceType )
   {
      // Do NOT PUT THE gLastEventTime change here, it will pick up the analog stick noise

      //copy the modifier into the new event
      mLastEvent.modifier = event->modifier;
      GuiControl *ctrl = static_cast<GuiControl*>( last() ); // Send events to the top level GUI
      const char *retval = "";

      if( event->objType == SI_BUTTON )
      {
         if( event->action == SI_MAKE )
         {
            switch( event->objInst )
            {
               case KEY_BUTTON0:
                  retval = Con::executef( ctrl, 1, "onA" );
                  break;
               case KEY_BUTTON1:
                  retval = Con::executef( ctrl, 1, "onB" );
                  break;

So, I am just going to fix the input message so that objType and ObjInst are not the same value when using the 360 Controller and then I will add the case statements for XI_A, XI_B, etc. so that this block of code will work with both the controller and a joystick.

Thanks again for your comments. I will post the code changes here once I have tested them.

Todd
#4
02/21/2007 (5:18 pm)
Ok,

With Pat's help, this was as simple as can be...
In case others would like to quickly add 360 Controller support to their GUIs here are the code changes:

In WinDirectInput.cpp replace buildXInputEvent with this version which properly sets objType:

inline void DInputManager::buildXInputEvent( U32 deviceInst, U16 objInst, U8 action, float fValue )
{
   InputEvent newEvent;

   newEvent.deviceType = XInputDeviceType;
   newEvent.deviceInst = deviceInst;
   newEvent.action = action;
   newEvent.fValue = fValue;
   newEvent.objInst = objInst;
   switch(objInst)
   {
		case XI_DPAD_UP:
		case XI_DPAD_DOWN:
		case XI_DPAD_LEFT:
		case XI_DPAD_RIGHT:
		case XI_START:
		case XI_BACK:
		case XI_LEFT_THUMB:
		case XI_RIGHT_THUMB:
		case XI_LEFT_SHOULDER: 
		case XI_RIGHT_SHOULDER: 
		case XI_LEFT_TRIGGER:
		case XI_RIGHT_TRIGGER:
		case XI_A:
		case XI_B:
		case XI_X: 
		case XI_Y:
			newEvent.objType = SI_BUTTON;
			break;
		case XI_THUMBLX:
		case XI_THUMBRX:
			newEvent.objType = SI_XAXIS;
			break;
		case XI_THUMBRY:
		case XI_THUMBLY:
			newEvent.objType = SI_YAXIS;
			break;
		default:
			newEvent.objType = objInst;
			break;
   }
   Game->postEvent( newEvent );
}

Then in GuiCanvas.cpp find the function processInputEvent (line 311) and find the XInputDeviceType section (line 521) and replace the SI_BUTTON section (line 530) with this version:

if( event->objType == SI_BUTTON )
      {
         if( event->action == SI_MAKE )
         {  
            switch( event->objInst )
            {
               case XI_A:  //TG-Adding 360 Controller support
               case KEY_BUTTON0:
                  retval = Con::executef( ctrl, 1, "onA" );
                  break;
               case XI_B:  //TG-Adding 360 Controller support
               case KEY_BUTTON1:
                  retval = Con::executef( ctrl, 1, "onB" );
                  break;
               case XI_X:  //TG-Adding 360 Controller support
               case KEY_BUTTON2:
                  retval = Con::executef( ctrl, 1, "onX" );
                  break;
               case XI_Y:	//TG-Adding 360 Controller support
               case KEY_BUTTON3:
                  retval = Con::executef( ctrl, 1, "onY" );
                  break;
               case XI_START:	//TG-Adding 360 Controller support
               case KEY_BUTTON4:
                  retval = Con::executef( ctrl, 1, "onStart" );
                  break;
               case XI_BACK://TG-Adding 360 Controller support
                case KEY_BUTTON5:
                  retval = Con::executef( ctrl, 1, "onBack" );
                  break;
               case XI_LEFT_SHOULDER: //TG-Adding 360 Controller support
               case KEY_BUTTON6:
                  retval = Con::executef( ctrl, 1, "onBlack" );
                  if( dStrcmp( retval, "" ) == 0 )
                     retval = Con::executef( ctrl, 1, "onLShoulder" );
                  break;
               case XI_RIGHT_SHOULDER: //TG-Adding 360 Controller support
               case KEY_BUTTON7:
                  retval = Con::executef( ctrl, 1, "onWhite" );
                  if( dStrcmp( retval, "" ) == 0 )
                     retval = Con::executef( ctrl, 1, "onRShoulder" );
                  break;
               case XI_LEFT_TRIGGER://TG-Adding 360 Controller support
               case KEY_BUTTON8:
                  retval = Con::executef( ctrl, 1, "onLTrigger" );
                  break;
               case XI_RIGHT_TRIGGER://TG-Adding 360 Controller support
               case KEY_BUTTON9:
                  retval = Con::executef( ctrl, 1, "onRTrigger" );
                  break;
               case XI_LEFT_THUMB://TG-Adding 360 Controller support
               case KEY_BUTTON10:
                  retval = Con::executef( ctrl, 1, "onLStick" );
                  break;
               case XI_RIGHT_THUMB://TG-Adding 360 Controller support
               case KEY_BUTTON11:
                  retval = Con::executef( ctrl, 1, "onRStick" );
                  break;
               //TG-BEGIN: Adding 360 Controller support
               case XI_DPAD_UP:
                  retval = Con::executef( ctrl, 1, "onUp" );
                  break;
               case XI_DPAD_DOWN:
                  retval = Con::executef( ctrl, 1, "onDown" );
                  break;
               case XI_DPAD_LEFT:
                  retval = Con::executef( ctrl, 1, "onLeft" );
                  break;
               case XI_DPAD_RIGHT:
                  retval = Con::executef( ctrl, 1, "onRight" );
                  break;
               //TG-END
               default:
	     return false;
            }

Now your GUIs will work with the 360 controller.

Pat, thanks again for the help.
Todd


edited for formatting wierdness
#5
02/22/2007 (11:30 am)
Thanks for posting your fix, Todd!
-Pat
#6
02/22/2007 (2:58 pm)
Thanks Todd, that's very helpful =)
#7
02/22/2007 (3:06 pm)
Cool!
#8
02/22/2007 (10:56 pm)
Turns out that making objInst/objType contain the correct values does require one more change.

In actionmap.cpp SI_MOVE events just use the objType. Presumably, this is because the mouse can only have an x, y, and z axis, so the objType was enough. However, with a 360 controller you have the x, y, and z axis for the left stick and then again for the right stick. So, knowing the axis isn't enough.

This change will allow the XInput changes to work with the actionMap while ensuring that the joystick functionality remains unchanged.

In actionmap.cpp we just need to change lines 1256 and 1261 to use objInst when it is an XInputDevice.

So change this:

// Joystick events...
         const Node* pNode = findNode( pEvent->deviceType, pEvent->deviceInst, pEvent->modifier,   pEvent->objType);  

         if ( pNode == NULL )
         {
            // Check to see if we clear the modifiers, do we find an action?
            if ( pEvent->modifier != 0 ) pNode = findNode( pEvent->deviceType, pEvent->deviceInst, 0, pEvent->objType); 
            if ( pNode == NULL ) return false;
         }

to this:

// Joystick events...
         const Node* pNode = findNode( pEvent->deviceType, pEvent->deviceInst, pEvent->modifier,   (pEvent->deviceType==JoystickDeviceType) ? pEvent->objType : pEvent->objInst);  //TG-Fixing XInput support

         if ( pNode == NULL )
         {
            // Check to see if we clear the modifiers, do we find an action?
            if ( pEvent->modifier != 0 ) pNode = findNode( pEvent->deviceType, pEvent->deviceInst, 0, (pEvent->deviceType==JoystickDeviceType) ? pEvent->objType : pEvent->objInst); //TG-Fixing XInput support
            if ( pNode == NULL ) return false;
         }

Sorry I missed this on the first go around...
Todd
#9
02/23/2007 (3:48 pm)
Yay, that rocks. I thought I was going to have fix that. You saved me some time ;)