DirectInput... identifying second thumbstick

Started by
6 comments, last by JohnRaptis 12 years, 5 months ago
Hi all,
I'm writing a dual-stick shooter game, which the user can either play with mouse+keyboard or with a dual-thumbstick joystick.

In enumerating the joystick... how can you determine which values go with the second thumbstick? The joystick I'm testing with puts the X value into lrZ, and the Y value into rglSlider[0]... I assume that's not "standard!"

Is there some way or DirectInput call that allows me to identify the second thumbstick?
Advertisement
There's (AFAIK, from my experience) no standard in this, it depends on the particular manufacturer. A Genius gamepad I tried indeed assigns one of the axes of the right analog stick as a slider.
The left stick is always (or usually) simply X and Y, but for the right one, I've already had these:

- Rx, Ry (Logitech, Xbox360 compatible)
- Rz, Slider (Genius)
- Z, Rz (Logitech)

I don't know of any way how to say which SW axis is assigned to a physical controller (right thumbstick), but you can use a callback which will let you know types of all axes on the device, so you'll at least know which axes do you have. Simply said, you will know for example that the gamepad has axes X, Y, Rz, Slider, but you'll not know which one belongs to which thumbstick (well, you can easily guess, but you won't be sure).

Just call

m_pJoystick->EnumObjects(EnumAxesCallback, NULL, DIDFT_AXIS);


And implement the EnumAxesCallback like this:

BOOL CALLBACK EnumAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, VOID* context)
{
if (instance->guidType == GUID_XAxis)
// we have X
else if (instance->guidType == GUID_YAxis)
// we have Y
else if (instance->guidType == GUID_ZAxis)
// we have Z
else if (instance->guidType == GUID_RxAxis)
// we have Rx
// etc.....

return DIENUM_CONTINUE;
}
Oh hey, thanks, that enum I think will be close enough for my purposes! I'll just leave a way for the user to tweak it, but that will probably get me through 99% of problems. Thanks again!
Update:

I have found a solution that has, so far, worked on every joystick I've thrown at it. For posterity, you can do as follows:

1. Enumerate all the axes
2. Ignore GUID_XAxis,GUID_YAxis -- assume they always apply to the first thumbstick, so bind them there, and ignore them in the enumeration.
3. IF in the enumeration you get GUID_RxAxis and GUID_RyAxis, assume they are the second thumbstick. They will come out of order for an XBox 360 joystick, so make sure you associate X with RxAxis and Y with RyAxis.
4. If you haven't gotten any of the above, take the first two enumerated axes that aren't GUID_XAxis or GUID_YAxis and use them, in order, as your X,Y for the second thumbstick.

Using this, I've got a working automatic solution for five joysticks that span ten years (including an old Thrustmaster model that I've seen complained about on Steam forums).

Some code, for posterity... (in this code, you get your joystick data into gJoystickState, and read the contents of the pointer to the axes to get the axis data).


DIJOYSTATE gJoystickState;
LONG *gJoystick_XAxis[2];
LONG *gJoystick_YAxis[2];

int gAxisCount=0;
int gSliderCount=0;
BOOL CALLBACK EnumAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, VOID* context)
{
if (gAxisCount>1) return DIENUM_CONTINUE;

//
// X and Y Axis... let's assume those ALWAYS apply to the first thumbstick, okay?
// I mean... it would be INSANE if the manufacturer didn't report it that way, no?
//
if (instance->guidType == GUID_XAxis) return DIENUM_CONTINUE;
if (instance->guidType == GUID_YAxis) return DIENUM_CONTINUE;


//
// If these exist, we WILL set our second thumbstick to them, because we'll
// assume the joystick manufacturer knew what he was doing...
//
if (instance->guidType == GUID_RxAxis) gJoystick_XAxis[1]=&gJoystickState.lRx;
if (instance->guidType == GUID_RyAxis) gJoystick_YAxis[1]=&gJoystickState.lRy;

//
// These are harder! Assume they come in "in order."
//
if (instance->guidType == GUID_ZAxis)
{
if (gAxisCount==0) if (!gJoystick_XAxis[1]) gJoystick_XAxis[1]=&gJoystickState.lZ;
if (gAxisCount==1) if (!gJoystick_YAxis[1]) gJoystick_YAxis[1]=&gJoystickState.lZ;
}
if (instance->guidType == GUID_RxAxis)
{
if (gAxisCount==0) if (!gJoystick_XAxis[1]) gJoystick_XAxis[1]=&gJoystickState.lRx;
if (gAxisCount==1) if (!gJoystick_YAxis[1]) gJoystick_YAxis[1]=&gJoystickState.lRx;
}
if (instance->guidType == GUID_RyAxis)
{
if (gAxisCount==0) if (!gJoystick_XAxis[1]) gJoystick_XAxis[1]=&gJoystickState.lRy;
if (gAxisCount==1) if (!gJoystick_YAxis[1]) gJoystick_YAxis[1]=&gJoystickState.lRy;
}
if (instance->guidType == GUID_RzAxis)
{
if (gAxisCount==0) if (!gJoystick_XAxis[1]) gJoystick_XAxis[1]=&gJoystickState.lRz;
if (gAxisCount==1) if (!gJoystick_YAxis[1]) gJoystick_YAxis[1]=&gJoystickState.lRz;
}
if (instance->guidType == GUID_Slider)
{
if (gAxisCount==0) if (!gJoystick_XAxis[1]) gJoystick_XAxis[1]=&gJoystickState.rglSlider[gSliderCount++];
if (gAxisCount==1) if (!gJoystick_YAxis[1]) gJoystick_YAxis[1]=&gJoystickState.rglSlider[gSliderCount++];
}
gAxisCount++;

return DIENUM_CONTINUE;
}

void FindThumbsticks()
{
gAxisCount=0;
gSliderCount=0;
gJoystick_XAxis[0]=&gJoystickState.lX;
gJoystick_YAxis[0]=&gJoystickState.lY;
gJoystick_XAxis[1]=NULL;
gJoystick_YAxis[1]=NULL;
yourJoystickDevice->EnumObjects(EnumAxesCallback, NULL, DIDFT_AXIS);
if (!gJoystick_XAxis[1]) gJoystick_XAxis[1]=&gJoystickState.lX;
if (!gJoystick_YAxis[1]) gJoystick_YAxis[1]=&gJoystickState.lY;
}
So simply said, the axes in the callback always come in this order?
left x
left y
right x
right y

I kinda thought/assumed it but never really tested it on even a single gamepad (I didn't need it). May be good to know, although I still don't think it's a rule.
But anyway, can be a good start - and better than nothing :)


Hm, when I think about it more - some gamepads have a mode switch and some modes as far as I remember make really "strange" things. I think it can even reassign the left stick so it WOULDN'T be X and Y. That way you as a programmer cannot be sure with nothing. I'll check it tomorow on a Genius one.
I strongly recommend you stop trying to support the hundred thousand dual stick controller types out there and simply work with the xbox 360 controller (xinput). You're almost guaranteed that your code will not work with all controller types the way you intended. "Oh noes, but I don't want to support only the xbox 360 controller, I want users to be able to use any dual stick controller" I hear you say. Fear not, for your users can use the freely available xbox 360 emulator and even be able to customize the mapping between their controller and the virtual xbox controller.

I strongly recommend you stop trying to support the hundred thousand dual stick controller types out there and simply work with the xbox 360 controller (xinput). You're almost guaranteed that your code will not work with all controller types the way you intended. "Oh noes, but I don't want to support only the xbox 360 controller, I want users to be able to use any dual stick controller" I hear you say. Fear not, for your users can use the freely available xbox 360 emulator and even be able to customize the mapping between their controller and the virtual xbox controller.


I don't agree with that. It's never good to force your customer into anything, to limit him anyhow.
"Hey, I have a brand new and perfectly working gamepad, but this silly game doesn't support it because it's not Xinput compatible. I couldn't care less about some freely available emulator, this game is just dead for me."

GTA IV is a bit different case, that's an AAA+ game and if you really want to play it, you are wiling to do a lot of stuff (install various community applications and create profiles there etc.). But a small project?

It isn't that complicated to make your project DirectInput compatible and thus supporting almost ANY controller. It doesn't have to be automatic, you can let the user configure the axes.
Do you have a Xbox360 compatible gamepad? Great for you, that's preffered, you can play directly. Do you have just DirectInput compatible one? Never mind, connect it and try it. Doesn't it work well? Let's go to a config screen, now please move your walking stick forward/backward, now left/right. Now move your lookaround stick up/down and now left/down. Thanks, you can use that gamepad from now.

So simply said, the axes in the callback always come in this order?
left x
left y
right x
right y


No. So far, left x, left y have always come first, but right y and right x come in reverse order on an XBox 360 controller, but come in the correct order on everything else I have here that uses them. So I don't think you can rely on that order, you have to do some AI for it (for instance, in my above program, if it finds the rx axis, it'll attach that to 'x' regardless of order arrived).

This topic is closed to new replies.

Advertisement