Page 1 of 2

Joysticks

Posted: Fri Jul 21, 2023 3:39 am
by coco2
I couldn't see a whole lot in the forum when I searched for this so thought I would post some thoughts on joystick implementation.

I have done some initial testing of joysticks with PureBasic but I'm having some difficulty.

With an Xbox One controller connected by USB cable I get 2 joysticks after calling InitJoystick()

0: Controller (Xbox One For Windows)
1: XBox 360 controller 1

When using bluetooth with the Xbox One controller I also get two devices:

0: Bluetooth XINPUT-compatible input device
1: XBox 360 controller 1

The second controller can be safely ignore as all readouts can be obtained from the first one. Is the reason for the second one appearing known?

When a SNES controller is connected with a USB adapter it is detected as one joystick:

0: SNES PC Game Pad

And a PS3 controller is also one:

0: PS3/PC Gamepad

Also when different controllers like Xbox and PS3 etc are connected the analogue stick readout varies and you need to read them using different "pad" values that varies between controller. The differences are expected since controllers vary in their design so it would be hard to come up with a uniform system.

What is the best way to work around this? Should my game keep a database of controller name strings so I can apply the appropriate settings for each controller? I think it would be too complex for the user to configure all this themselves.

Edit:
I tried connecting two joysticks and I got this result:
Input device 0: Bluetooth XINPUT-compatible input device detected
Input device 1: Controller (Xbox One For Windows) detected
Input device 2: XBox 360 controller 1 detected
Input device 3: XBox 360 controller 2 detected

The two joysticks are device 0 and 1, I'm not sure why 2 and 3 are detected. Is this something to do with the OS?

Re: Joysticks

Posted: Fri Jul 21, 2023 4:32 pm
by benubi
The following is speculative. I don't know why it is this way, and what you mean with 2 and 3 should not be there or not working, I think they should.

As it seems 0 & 1 and the device general group/description and the 2 & 3 are the specific description and names of those devices.

I think it's just two different names, aka aliases, for the same thing. First is the general generated name like "this is a mouse" and the second is the more specific "microsoft mouse 192323" name.

It seems that the first name version also refers to the interfaces used to connect the device (bluetooth) and it's type, while the second pair describes the specific product name.

I suppose MS has an internal DB to look up ID's from the devices. If there's a match for a specific entry (the alias with "real product name" for it) is added to maybe simplify finding that entry for the users. (easier to search & find Xbox controller than for XINPUT-compatible/bluetooth in a drop down list).

I think 0+2 and 1+3 they should work identically.

The SNES and PS3/PC gamepads may have no specific entry or there's no manufacturer info about them, so they stay with generic names and without alias/"real name" or they only stay there with their real name, because there is no alias for their "blueetooth interface" because USB devices are the de-facto standard to be expected (and the other interfaces pretend to be USB devices and that's why they are listed so they can be found). Maybe installing drivers or bundled software will add their full names in the list. If the aliases behave identically and there's no problem, you are just being confused as a user :) You only need to remember the name of the device he wants to use anyway.

Re: Joysticks

Posted: Fri Jul 21, 2023 4:42 pm
by benubi
I believe you get only additional entries when there's a "virtual USB" device, e.g. your bluetooth device pretending to be a USB device.

I plugged my "twin gamepads" and there's one entry for each, no aliases. I'm rather confident it's because of that internal bluetooth-USB bridge.

Re: Joysticks

Posted: Sat Jul 22, 2023 1:58 am
by coco2
The second one does not appear to be usable because the Z axis (triggers) only shows the left trigger and does not show the two buttons on the top next to the triggers. I don't know why it calls it an Xbox 360 controller since it is an Xbox One controller. What do you mean it's because of the bluetooth bridge? If I plug it in with a USB cable it still has two. If I completely unplug the bluetooth receiver from the PC it still shows two entries.

Edit: I have further updates. I tried an actual Xbox 360 controller connected with a third-party wireless dongle (not a genuine Microsoft one) and it shows up and just one device: "XBox 360 controller". I figured out the Xbox 360 triggers work a bit differently, the right trigger is there under pad 1. I am still unsure why the Xbox One controller appears as two controllers (the second being an Xbox 360 controller).

Re: Joysticks

Posted: Sat Jul 22, 2023 6:23 pm
by benubi
I suppose it proposes XBox 360 controller for backward compatibility. Your controller can be plugged in the 360.
Perhaps the XBox checks the device Id strings and a 360 only wants controllers that have xbox and "360" in the name. It's a driver feature or issue.

Re: Joysticks

Posted: Sun Jul 23, 2023 1:26 am
by coco2
I'm not sure the Xbox 360 can use Xbox One controllers, I've read a website saying you can't. Perhaps there is a reason it offers both devices, since the Xbox 360 version is actually better in my opinion, it allows both triggers to be pulled in at once but the Xbox One version puts the triggers on only one axis (so when you pull both triggers it reads zero in the middle). When the Xbox 360 version has both triggers pulled all the way in it reads 1000 on separate axes.

If it's a feature I have to work around then it might be possible. I connected two Xbox One controllers via bluetooth and one XBox 360 controller via it's own wireless adapter. This is the list of connected joysticks:

Input device 0: Bluetooth LE XINPUT compatible input device detected
Input device 1: Bluetooth LE XINPUT compatible input device detected
Input device 2: XBox 360 controller 1 detected
Input device 3: XBox 360 controller 2 detected
Input device 4: XBox 360 controller 3 detected

So if "Bluetooth LE XINPUT compatible input device" is detected, then select either that device or the first instance of Xbox 360 controller (depending on whether I want the Xbox 360 version or not). That would also work for the second one too. Then the third Xbox 360 controller is an actual 360 controller so it needs to be recognised (device 4).

Edit: I have just confirmed that the second Xbox 360 controller that is available gets added in the order the controllers are turned on. Since it is impossible to know the order I conclude that it is impossible to have automatic detection of controllers using Purebasic (maybe it is at the OS level).

Re: Joysticks

Posted: Wed Aug 02, 2023 7:02 am
by coco2
Is there such a thing as a GUID for controllers/joysticks? Does anyone know how to get it? I'm thinking the string provided by JoystickName() is too generic in some cases, I have controllers that identify as "USB gamepad" and "Controller".

Re: Joysticks

Posted: Wed Aug 02, 2023 5:40 pm
by benubi
I suppose they all have a GUID somewhere but you would have to use some API to get to it, on windows probably the registry and/or DirectX would have all you need. I haven't done something like that yet. Perhaps there's a platform independent way to find it out, but I doubt it. There's also the possibility that a few share "no name" GUID's and that would be why they have very generic names?

Maybe as a work around if the names are unique enough you could make them editable and show an alias. This would be what you want to achieve with or without the GUID anyway. I think the most important thing is that the user has all the options, if something works or not it should be easily recognizable to him and he takes the next joystick from the menu.

Re: Joysticks

Posted: Thu Aug 03, 2023 4:11 am
by coco2
On further googling, I don't think GUID is what I want, I think that is a unique ID. What I want is the manufacturer ID and device ID. I think the following will produce those:

Code: Select all

joyGetDevCaps_(Joystick_ID, joyCaps.JOYCAPS, SizeOf(JOYCAPS))
Debug joycaps\wMid
Debug joycaps\wPid
I think this will do what I am trying to do, which is uniquely identify each different model of controller plugged in. Does anyone know how to do this in Mac and Linux?

Edit: the joystick api doesn't see Xbox One controllers as two controllers. Should I submit a bug report for the built in PB joystick functions seeing them as two devices?

Re: Joysticks

Posted: Sat Aug 05, 2023 6:22 pm
by kenmo
Hi coco2,

I went through this a few years ago... attempting to identify joysticks, and map them all to standard interface, stored as a database... I had a basic version of it working... then I found a much better way that already exists.

If you're serious about supporting a variety of game controllers, I really REALLY recommend using the free cross-platform SDL library, which can handle more joysticks, with more I/O, than the default PB library, plus rumble feedback and motion sense. See "Joystick" functions.

But more importantly, the "Game Controller" library extends it further, and remaps all known gamepad-like joysticks to a standard interface (Left Analog Stick, Right Shoulder Trigger, etc.) for easy game programming. The database is community-maintained and has been used in hobbyist, indie, professional games for many years... I think Steam uses it too? See "GameController" functions.

Probably most people associate SDL with screens and graphics rendering and audio, but you can definitely use its GameController functions independent from everything else. (So, you can use SDL's advanced game controller input to control a PB program! I've done this before.)

Take a look! Unfortunately it seems they've revamped their help Wiki, and a ton of information is missing now????

Re: Joysticks

Posted: Sun Aug 06, 2023 1:16 am
by coco2
Hi Kenmo,

I did spend some time making a joystick database in my game only to realise it relies on JoystickName() as the only way to identify the joysticks which is not ideal. I will take a look at SDL, do you know of any documentation showing how to use it in PB?

Re: Joysticks

Posted: Sun Aug 06, 2023 8:12 pm
by kenmo
Unfortunately I don't have a simple SDL2 / GameController example ready, maybe I could write a small one this week.
Assuming you're on Windows, you just need to grab the latest SDL2.dll or SDL2.lib and start loading or importing functions into PB...


But here are some links I can point you to:

My own PB includefile for binding some SDL2 functions (not all, just ones I've used, including Joystick and GameController and Rumble)
https://raw.githubusercontent.com/kenmo ... r/PSDL.pbi

GPI's similar work including some examples
viewtopic.php?t=76052

This is older, but J. Baker posted a simple SDL example for reading Joystick buttons and axes
viewtopic.php?t=49667

Re: Joysticks

Posted: Fri Aug 11, 2023 1:16 am
by kenmo
OK, here is a very quick example of reading "Game Controllers" using SDL2.dll on Windows...

It should recognize whatever gamepads you plug in, and remap them all to a standard "Xbox like" mapping (Left Stick, Right Stick, Analog Triggers, 15 buttons...)

This is bad coding style, I open SDL2.dll and continuously call DLL functions by their string names... but at least you don't need any IncludeFiles to get started and test it!


Better would be to use ImportC for the functions you need, or stay with the DLL but use Prototypes set to the function pointers...
See my PSDL.pbi linked above to see what I'm talking about.


Code: Select all

; 2023-08-10 - quick SDL2.dll gamepad example by kenmo
;
; get the latest SDL2.dll from: https://github.com/libsdl-org/SDL/releases/latest


If Not OpenLibrary(0, "SDL2.dll")
  MessageRequester("Error", "SDL2.dll not found!")
  End
EndIf

; SDL Constants...

Enumeration ; flags for SDL_Init()
  #SDL_INIT_TIMER          = $00000001
  #SDL_INIT_AUDIO          = $00000010
  #SDL_INIT_VIDEO          = $00000020 ; implies EVENTS
  #SDL_INIT_JOYSTICK       = $00000200 ; implies EVENTS
  #SDL_INIT_HAPTIC         = $00001000
  #SDL_INIT_GAMECONTROLLER = $00002000 ; implies JOYSTICK
  #SDL_INIT_EVENTS         = $00004000
  #SDL_INIT_NOPARACHUTE    = $00100000
  #SDL_INIT_EVERYTHING     = #SDL_INIT_TIMER | #SDL_INIT_AUDIO | #SDL_INIT_VIDEO | #SDL_INIT_EVENTS | #SDL_INIT_JOYSTICK | #SDL_INIT_HAPTIC | #SDL_INIT_GAMECONTROLLER
EndEnumeration

Enumeration ; SDL_GameControllerButton
  #SDL_CONTROLLER_BUTTON_INVALID = -1
  #SDL_CONTROLLER_BUTTON_A
  #SDL_CONTROLLER_BUTTON_B
  #SDL_CONTROLLER_BUTTON_X
  #SDL_CONTROLLER_BUTTON_Y
  #SDL_CONTROLLER_BUTTON_BACK
  #SDL_CONTROLLER_BUTTON_GUIDE
  #SDL_CONTROLLER_BUTTON_START
  #SDL_CONTROLLER_BUTTON_LEFTSTICK
  #SDL_CONTROLLER_BUTTON_RIGHTSTICK
  #SDL_CONTROLLER_BUTTON_LEFTSHOULDER
  #SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
  #SDL_CONTROLLER_BUTTON_DPAD_UP
  #SDL_CONTROLLER_BUTTON_DPAD_DOWN
  #SDL_CONTROLLER_BUTTON_DPAD_LEFT
  #SDL_CONTROLLER_BUTTON_DPAD_RIGHT
  #SDL_CONTROLLER_BUTTON_MAX
EndEnumeration

Enumeration ; SDL_GameControllerAxis
  #SDL_CONTROLLER_AXIS_INVALID = -1
  #SDL_CONTROLLER_AXIS_LEFTX
  #SDL_CONTROLLER_AXIS_LEFTY
  #SDL_CONTROLLER_AXIS_RIGHTX
  #SDL_CONTROLLER_AXIS_RIGHTY
  #SDL_CONTROLLER_AXIS_TRIGGERLEFT
  #SDL_CONTROLLER_AXIS_TRIGGERRIGHT
  #SDL_CONTROLLER_AXIS_MAX
EndEnumeration







Global NewMap GCForJoyIndex.i()

Procedure.s NameForGCButton(btn)
  Select btn
    Case #SDL_CONTROLLER_BUTTON_A : ProcedureReturn "A"
    Case #SDL_CONTROLLER_BUTTON_B : ProcedureReturn "B"
    Case #SDL_CONTROLLER_BUTTON_X : ProcedureReturn "X"
    Case #SDL_CONTROLLER_BUTTON_Y : ProcedureReturn "Y"
    Case #SDL_CONTROLLER_BUTTON_BACK : ProcedureReturn "Bk"
    Case #SDL_CONTROLLER_BUTTON_DPAD_DOWN : ProcedureReturn "v"
    Case #SDL_CONTROLLER_BUTTON_DPAD_LEFT : ProcedureReturn "<"
    Case #SDL_CONTROLLER_BUTTON_DPAD_RIGHT : ProcedureReturn ">"
    Case #SDL_CONTROLLER_BUTTON_DPAD_UP : ProcedureReturn "^"
    Case #SDL_CONTROLLER_BUTTON_GUIDE : ProcedureReturn "Gd"
    Case #SDL_CONTROLLER_BUTTON_LEFTSHOULDER : ProcedureReturn "[L]"
    Case #SDL_CONTROLLER_BUTTON_LEFTSTICK : ProcedureReturn "(L)"
    Case #SDL_CONTROLLER_BUTTON_RIGHTSHOULDER : ProcedureReturn "[R]"
    Case #SDL_CONTROLLER_BUTTON_RIGHTSTICK : ProcedureReturn "(R)"
    Case #SDL_CONTROLLER_BUTTON_START : ProcedureReturn "St"
    Default : ProcedureReturn "?"
  EndSelect
EndProcedure

Procedure UpdateControllerList()
  CallCFunction(0, "SDL_PumpEvents")
  ;CallCFunction(0, "SDL_JoystickUpdate")
  ;CallCFunction(0, "SDL_GameControllerUpdate")
  
  ClearGadgetItems(0)
  NumJoysticks = CallCFunction(0, "SDL_NumJoysticks")
  NumGameControllers = 0
  For i = 0 To NumJoysticks - 1
    If (CallCFunction(0, "SDL_IsGameController", i))
      If (FindMapElement(GCForJoyIndex(), Str(i)))
        *GC = GCForJoyIndex()
      Else
        *GC = CallCFunction(0, "SDL_GameControllerOpen", i)
      EndIf
      If (*GC)
        *NamePtr = CallCFunction(0, "SDL_GameControllerName", *GC)
        If (*NamePtr)
          Text.s = PeekS(*NamePtr, -1, #PB_UTF8)
          RowText.s = Text
          
          axis.w = CallCFunction(0, "SDL_GameControllerGetAxis", *GC, #SDL_CONTROLLER_AXIS_LEFTX)
          RowText + #LF$ + StrF(axis/32768.0,2)
          axis.w = CallCFunction(0, "SDL_GameControllerGetAxis", *GC, #SDL_CONTROLLER_AXIS_LEFTY)
          RowText + "," + StrF(axis/32768.0,2)
          
          axis.w = CallCFunction(0, "SDL_GameControllerGetAxis", *GC, #SDL_CONTROLLER_AXIS_RIGHTX)
          RowText + #LF$ + StrF(axis/32768.0,2)
          axis.w = CallCFunction(0, "SDL_GameControllerGetAxis", *GC, #SDL_CONTROLLER_AXIS_RIGHTY)
          RowText + "," + StrF(axis/32768.0,2)
          
          axis.w = CallCFunction(0, "SDL_GameControllerGetAxis", *GC, #SDL_CONTROLLER_AXIS_TRIGGERLEFT)
          RowText + #LF$ + StrF(axis/32768.0,2)
          axis.w = CallCFunction(0, "SDL_GameControllerGetAxis", *GC, #SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
          RowText + "," + StrF(axis/32768.0,2)
          
          Text = ""
          For j = 0 To #SDL_CONTROLLER_BUTTON_MAX - 1
            If (CallCFunction(0, "SDL_GameControllerGetButton", *GC, j))
              Text + NameForGCButton(j)
            Else
              Text + "-"
            EndIf
          Next j
          RowText + #LF$ + Text
          
          AddGadgetItem(0, NumGameControllers, RowText)
          NumGameControllers + 1
        EndIf
        ;CallCFunction(0, "SDL_GameControllerClose", *GC)
      EndIf
    EndIf
  Next i
  If (NumGameControllers = 0)
    AddGadgetItem(0, 0, "Please plug in a Gamepad!")
  EndIf
EndProcedure











If CallCFunction(0, "SDL_Init", #SDL_INIT_EVERYTHING) = 0 ; 0 = no error
  
  OpenWindow(0, 0, 0, 640, 240, "SDL2 GameControllers", #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
  ListIconGadget(0, 0, 0, WindowWidth(0), WindowHeight(0), "Name", 180, #PB_ListIcon_GridLines)
  AddGadgetColumn(0, 1, "Left Stick", 80)
  AddGadgetColumn(0, 2, "Right Stick", 80)
  AddGadgetColumn(0, 3, "Triggers", 80)
  AddGadgetColumn(0, 4, "Buttons", 160)
  AddWindowTimer(0, 0, 20)
  UpdateControllerList()
  Repeat
    Event = WaitWindowEvent(10)
    If (Event = #PB_Event_Timer)
      UpdateControllerList()
    EndIf
  Until (Event = #PB_Event_CloseWindow)
  
Else
  MessageRequester("Error", "Failed to initialize SDL2")
EndIf


Re: Joysticks

Posted: Fri Aug 11, 2023 9:51 am
by coco2
Thanks for the example I'll look into it. It solved the problem of two controllers appearing for the Xbox One controller however now the problem is happening with the Xbox 360 controller. The interesting this is that the 3rd party wireless adapter (the non Microsoft one that says "XBOX 360" on it) causes two controllers to appear, but the official Microsoft adapter (the one that says "Microsoft") only shows one controller. It also solved the problem of the Xbox One controller triggers being on the same "axis" meaning each trigger has an individual reading of it's position, but before the triggers were on a sort of single axis.

I don't know what prototypes set to function pointers means can you show that?

Re: Joysticks

Posted: Fri Aug 11, 2023 12:21 pm
by kenmo
Yeah, in PureBasic "Prototypes" are a more advanced feature, it's like calling a function or procedure except the function is loaded at run-time from a DLL (or equivalent on Mac/Linux).

For example, if you're programming a game using SDL2.dll for gamepad input, it's not efficient to do this all the time:

Code: Select all

ButtonState = CallCFunction(Library, "SDL_GameControllerGetButton", *GameController, Button)
Prototypes are better/faster, it just takes more setup, plus has other benefits, look up "Prototype" in PB help:

Code: Select all

; Do this once, earlier in the program
PrototypeC.i _SDL_GameControllerGetButton(*gamecontroller.SDL_GameController, button.i)
Global SDL_GameControllerGetButton._SDL_GameControllerGetButton = GetFunction(Library, "SDL_GameControllerGetButton")

; and then throughout your code, you can just call
ButtonState = SDL_GameControllerGetButton(*GameController, Button)
I'm glad it solved one of your problems... but not the other?! Does Windows device settings show it as two controllers too?