It is currently Tue Oct 24, 2017 1:37 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: Gamepad (Windows 2000/XP/Vista/7/8/10)
PostPosted: Thu Oct 30, 2014 5:38 am 
Offline
Addict
Addict
User avatar

Joined: Sat Feb 19, 2005 5:05 pm
Posts: 1769
Location: Norway
Here are three ways to handle a gamepad on Windows. Hopefully this is a nice starting point for your own projects.

The methods are Xinput and PureBasic (DirectInput). The DirectInput method has two different types supported.
The mappings are based on my Speedlink STRIKE FX Gamepad (Model SL-6537-BK) which is a Xinput gamepad with a mode switch to make it a DirectInput gamepad (and the ability to toggle digital/analog in that mode). I do not have other gamepads to test with sadly.

The three examples uses a XINPUT_NORMALIZE structure that present the gamepad state the same way with all three methods, thus one should easily be able to let the user choose which mapping/gamepad mode they wish to use.
The structure is normalized to the floating point range -1.0 to +1.0 with 0.0 being center. In cases like Left and Right Trigger the value range is 0.0 to 1.0, but in the case of a DirectInput (Type 1) the value is either 0.0 or 1.0 (no range) as they are considered digital buttons.
Other buttons are digital only and a button mask is used for those, a handy macro makes it easy to get the state of a button.

The Xinput code also has basic rumble/forcefeedback.

Do note that the DirectInput (PureBasic native) mapping is based on the physical layout of the gamepad controller, and depending on if this is a DirectInput gamepad or a Xinput gamepad the Type1 and Type2 methods vary widely (reversed, rotated or mapped rather weird).
It is possible that other gamepads out there would require another type of mapping, if that is the case then please post those in this thread and call them Type3 and Type4 etc.
Hopefully Type1 and Type2 are the two most common ones.

Please note that the test gamepad was properly calibrated in Windows first, and that this was on Windows 7 and it seems like Windows might have applied some calibration adjustments (the gamepad had what appeared to be 0 deadzones so that may have helped as well).
No special gamepad drivers/software was installed, only whatever drives Windows found on it's own.

Also note that a gamepad may not be able to run the low and high motors at very low speeds, the low frequency motor and high frequency motors may have different minimum values as well.

No XInput caps are applied for two reasons, for one not all gamepads provide them thus the hardcoded defaults are used instead, secondly on the test gamepad I found the thumb stick range to not really match (a larger range of values was returned than the caps info stated there should be), this could be a calibration vs uncalibrated difference, but I don't think that is the issue.

For the DirectInput Type1 and Type2 methods there is also a small "hack" I added to handle a centering issue, the issue could be just this gamepad model but I seem to recall the same issue on a previous gamepad I had a long time ago. The hack is mostly harmless though and the user/gamer will never notice it. It is only the Z axis (aka throttle) that behaves this way. The range is -32768 to +32767 and normally 0 is center, but in this case both 1 and 0 are center, but DirectInput reports it as +1 instead, so if your ship or car seems to drive or drift on it's own in some games, then this may be the reason.

Tip! Allow the user/gamer to set and adjust deadzone. Remember that a stick for example has a center deadzone and a edge deadzone and they should be possible to set independently (ideally).

Code:
;xinput.pb
;(c) 2014 Roger Hågensen
;zlib License, http://en.wikipedia.org/wiki/Zlib_License

;This code is what you would use if the user selects "Xinput" controller mapping in your software.

;References
;http://msdn.microsoft.com/en-us/library/windows/desktop/hh405053.aspx

EnableExplicit

Prototype XInputGetState(dwUserIndex.l, *pState)
Prototype XInputSetState(dwUserIndex.l, *pVibration)

#XINPUT_GAMEPAD_DPAD_UP            = $0001
#XINPUT_GAMEPAD_DPAD_DOWN         = $0002
#XINPUT_GAMEPAD_DPAD_LEFT         = $0004
#XINPUT_GAMEPAD_DPAD_RIGHT         = $0008
#XINPUT_GAMEPAD_START            = $0010
#XINPUT_GAMEPAD_BACK               = $0020
#XINPUT_GAMEPAD_LEFT_THUMB         = $0040
#XINPUT_GAMEPAD_RIGHT_THUMB      = $0080
#XINPUT_GAMEPAD_LEFT_SHOULDER      = $0100
#XINPUT_GAMEPAD_RIGHT_SHOULDER   = $0200
#XINPUT_GAMEPAD_A                  = $1000
#XINPUT_GAMEPAD_B                  = $2000
#XINPUT_GAMEPAD_X                  = $4000
#XINPUT_GAMEPAD_Y                  = $8000

Structure XINPUT_GAMEPAD
   wButtons.w          ;See #XINPUT_GAMEPAD_ masks.
   bLeftTrigger.a      ;A value of 0 to 255.
   bRightTrigger.a   ;A value of 0 to 255.
   sThumbLX.w         ;Left thumbstick x-axis value, -32768 to +32767, 0 is centered.
   sThumbLY.w         ;Left thumbstick y-axis value, -32768 to +32767, 0 is centered.
   sThumbRX.w         ;Right thumbstick x-axis value, -32768 to +32767, 0 is centered.
   sThumbRY.w         ;Right thumbstick y-axis value, -32768 to +32767, 0 is centered.
EndStructure

Structure XINPUT_STATE
   dwPacketNumber.l
   Gamepad.XINPUT_GAMEPAD
EndStructure

Structure XINPUT_VIBRATION
   wLeftMotorSpeed.w ;Low frequency rumble, 0 to 65535, 0 is off and 65535 is 100% speed.
   wRightMotorSpeed.w ;High frequency rumble, 0 to 65535, 0 is off and 65535 is 100% speed.
EndStructure

Structure XINPUT_NORMALIZED
   buttons.l   ;See #XINPUT_GAMEPAD_ masks.
   triggerl.f   ;0.0 to 1.0
   triggerr.f   ;0.0 to 1.0
   thumblx.f   ;Left thumbstick x-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbly.f   ;Left thumbstick y-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbrx.f   ;Right thumbstick x-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbry.f   ;Right thumbstick y-axis value, -1.0 to +1.0, 0.0 is centered.
EndStructure

Structure XINPUT_XINPUT
   dll.i
   version.i
   *getstate.XInputGetState
   *setstate.XInputSetState
   state.XINPUT_STATE[4]
   normalized.XINPUT_NORMALIZED[4]
EndStructure

Macro Xinput_Button(buttons, mask)
   Bool(buttons & mask)
EndMacro

Procedure.i Xinput_Init()
   Protected *xinput.XINPUT_XINPUT

   *xinput = AllocateMemory(SizeOf(XINPUT_XINPUT))
   If *xinput = #False
      ProcedureReturn #Null
   EndIf

   *xinput\dll = OpenLibrary(#PB_Any, "xinput1_4.dll") ;Windows 8+

   *xinput\version = 14
   If *xinput\dll = #False
      *xinput\dll = OpenLibrary(#PB_Any, "xinput1_3.dll") ;DirectX SDK
      *xinput\version = 13
   EndIf
   If *xinput\dll = #False
      *xinput\dll = OpenLibrary(#PB_Any, "xinput1_2.dll") ;DirectX SDK
      *xinput\version = 12
   EndIf
   If *xinput\dll = #False
      *xinput\dll = OpenLibrary(#PB_Any, "xinput1_1.dll") ;DirectX SDK
      *xinput\version = 11
   EndIf
   If *xinput\dll = #False
      *xinput\dll = OpenLibrary(#PB_Any, "xinput9_1_0.dll") ;Windows Vista/7/8
      *xinput\version = 10
   EndIf
   If *xinput\dll = #False
      *xinput\version = 0
   EndIf

   *xinput\getstate = GetFunction(*xinput\dll, "XInputGetState")
   If *xinput\getstate = #Null
      *xinput\version = 0
   EndIf

   *xinput\setstate = GetFunction(*xinput\dll, "XInputSetState")
   If *xinput\setstate = #Null
      *xinput\version = 0
   EndIf
   
   If (*xinput\version = 0) And (*xinput\dll <> #Null)
      CloseLibrary(*xinput\dll)
   EndIf
   If *xinput\version = 0
      FreeMemory(*xinput)
      *xinput = #Null
   EndIf

   ProcedureReturn *xinput
EndProcedure

Procedure.i Xinput_Free(*xinput.XINPUT_XINPUT)
   If *xinput = #Null
      ProcedureReturn #Null
   EndIf
   
   If *xinput\dll <> #Null
      If IsLibrary(*xinput\dll)
         CloseLibrary(*xinput\dll)
      EndIf
      *xinput\dll = #Null
   EndIf
   
   FreeMemory(*xinput)

   ProcedureReturn #Null
EndProcedure

Procedure.i Xinput_Version(*xinput.XINPUT_XINPUT)
   ProcedureReturn *xinput\version
EndProcedure

#ERROR_DEVICE_NOT_CONNECTED = 1167

Procedure.i Xinput_Get(*xinput.XINPUT_XINPUT, index.l)
   Protected error.i
   If (index < 0) Or (index > 3)
      ProcedureReturn #Null
   EndIf
   ZeroMemory_(*xinput\state[index], SizeOf(XINPUT_STATE))
   ZeroMemory_(*xinput\normalized[index], SizeOf(XINPUT_NORMALIZED))
   error = *xinput\getstate(index, *xinput\state[index])
   If error <> #ERROR_SUCCESS
      ProcedureReturn #Null
   EndIf
   *xinput\normalized[index]\buttons = *xinput\state[index]\Gamepad\wButtons
   *xinput\normalized[index]\triggerl = *xinput\state[index]\Gamepad\bLeftTrigger / 255.0
   *xinput\normalized[index]\triggerr = *xinput\state[index]\Gamepad\bRightTrigger / 255.0
   If *xinput\state[index]\Gamepad\sThumbLX < 0
      *xinput\normalized[index]\thumblx = *xinput\state[index]\Gamepad\sThumbLX / 32768.0
   Else
      *xinput\normalized[index]\thumblx = *xinput\state[index]\Gamepad\sThumbLX / 32767.0
   EndIf
   If *xinput\state[index]\Gamepad\sThumbLY < 0
      *xinput\normalized[index]\thumbly = *xinput\state[index]\Gamepad\sThumbLY / 32768.0
   Else
      *xinput\normalized[index]\thumbly = *xinput\state[index]\Gamepad\sThumbLY / 32767.0
   EndIf
   If *xinput\state[index]\Gamepad\sThumbRX < 0
      *xinput\normalized[index]\thumbrx = *xinput\state[index]\Gamepad\sThumbRX / 32768.0
   Else
      *xinput\normalized[index]\thumbrx = *xinput\state[index]\Gamepad\sThumbRX / 32767.0
   EndIf
   If *xinput\state[index]\Gamepad\sThumbRY < 0
      *xinput\normalized[index]\thumbry = *xinput\state[index]\Gamepad\sThumbRY / 32768.0
   Else
      *xinput\normalized[index]\thumbry = *xinput\state[index]\Gamepad\sThumbRY / 32767.0
   EndIf
   ProcedureReturn *xinput\normalized[index]
EndProcedure

Procedure.i Xinput_Vibrate(*xinput.XINPUT_XINPUT, index.l, low.f, high.f)
   Protected error.i, motor.XINPUT_VIBRATION
   If (index < 0) Or (index > 3)
      ProcedureReturn #Null
   EndIf
   motor\wLeftMotorSpeed = low * 65535.0
   motor\wRightMotorSpeed = high * 65535.0
   error = *xinput\setstate(index, motor)
   If error <> #ERROR_SUCCESS
      ProcedureReturn #Null
   EndIf
   ProcedureReturn #ERROR_SUCCESS
EndProcedure

;************************************************************************
;Example
Define *xinput, *state.XINPUT_NORMALIZED

If OpenConsole("Xinput Test") = #False
   End
EndIf
EnableGraphicalConsole(#True)

*xinput = Xinput_Init()
If *xinput = #Null
   PrintN("Xinput_Init() failed.")
   End
EndIf

;Note that with DirectInput the Left and Right triggers are considered being on the same (Z) axis, with Xinput they are not.

; If a Xinput gamepad is connected,
; the expected results with Xinput_Get() are...
; Button A: 0 or 1
; Button B: 0 or 1
; Button X: 0 or 1
; Button Y: 0 or 1
; Button Left Shoulder: 0 or 1
; Button Right Shoulder: 0 or 1
; Button Back: 0 or 1
; Button Start: 0 or 1
; Button Left Thumb: 0 or 1
; Button Right Thumb: 0 or 1
; Button D-Pad Left: 0 or 1
; Button D-Pad Up: 0 or 1
; Button D-Pad Right: 0 or 1
; Button D-Pad Down: 0 or 1
; Left Trigger: 0.0 to 1.0
; Right Trigger: 0.0 to 1.0
; Left Stick X: -1.0 (left) to 0.0 (center) to 1.0 (right)
; Left Stick Y: -1.0 (down) to 0.0 (center) to 1.0 (up)
; Right Stick X: -1.0 (left) to 0.0 (center) to 1.0 (right)
; Right Stick Y: -1.0 (down) to 0.0 (center) to 1.0 (up)

Define timeold.l, timenew.l, vibrate.i

timenew = ElapsedMilliseconds()
timeold = timenew

Repeat
   timenew = ElapsedMilliseconds()
   *state = Xinput_Get(*xinput, 0)
   If *state
      ClearConsole()
      PrintN("Button A: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_A)))
      PrintN("Button B: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_B)))
      PrintN("Button X: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_X)))
      PrintN("Button Y: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_Y)))
      PrintN("Button Left Shoulder: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_LEFT_SHOULDER)))
      PrintN("Button Right Shoulder: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_RIGHT_SHOULDER)))
      PrintN("Button Back: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_BACK)))
      PrintN("Button Start: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_START)))
      PrintN("Button D-Pad Left: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_LEFT)))
      PrintN("Button D-Pad Up: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_UP)))
      PrintN("Button D-Pad Right: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_RIGHT)))
      PrintN("Button D-Pad Down: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_DOWN)))
      PrintN("Button Left Thumb: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_LEFT_THUMB)))
      PrintN("Button Right Thumb: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_RIGHT_THUMB)))
      PrintN("Left Trigger: " + StrF(*state\triggerl, 7) )
      PrintN("Right Trigger: " + StrF(*state\triggerr, 7))
      PrintN("Left Stick X: " + StrF(*state\thumblx, 7))
      PrintN("Left Stick Y: " + StrF(*state\thumbly, 7))
      PrintN("Right Stick X: " + StrF(*state\thumbrx, 7))
      PrintN("Right Stick Y: " + StrF(*state\thumbry, 7))
      PrintN("")
      PrintN("Press enter to stop.")
   Else
      PrintN("Error")
      Break
   EndIf
   If (timenew - timeold) >= 2000
      timeold = timenew
      If vibrate = 1
         Xinput_Vibrate(*xinput, 0, 0.0, 0.025)
         vibrate = 0
      Else
         Xinput_Vibrate(*xinput, 0, 0.075, 0.0)
         vibrate = 1
      EndIf
   EndIf
   Delay(100)
Until InKey() <> ""
Xinput_Vibrate(*xinput, 0, 0.0, 0.0)

PrintN("")
PrintN("Xinput DLL Version: " + StrD(Xinput_Version(*xinput) * 0.1, 1))
PrintN("Press enter to quit.")
Input()

*xinput = Xinput_Free(*xinput)
CloseConsole()
End


Code:
;pbinput1.pb
;(c) 2014 Roger Hågensen
;zlib License, http://en.wikipedia.org/wiki/Zlib_License

;This code is what you would use if the user selects "Type 1" controller mapping in your software.

EnableExplicit

#XINPUT_GAMEPAD_DPAD_UP            = $0001
#XINPUT_GAMEPAD_DPAD_DOWN         = $0002
#XINPUT_GAMEPAD_DPAD_LEFT         = $0004
#XINPUT_GAMEPAD_DPAD_RIGHT         = $0008
#XINPUT_GAMEPAD_START            = $0010
#XINPUT_GAMEPAD_BACK               = $0020
#XINPUT_GAMEPAD_LEFT_THUMB         = $0040
#XINPUT_GAMEPAD_RIGHT_THUMB      = $0080
#XINPUT_GAMEPAD_LEFT_SHOULDER      = $0100
#XINPUT_GAMEPAD_RIGHT_SHOULDER   = $0200
#XINPUT_GAMEPAD_A                  = $1000
#XINPUT_GAMEPAD_B                  = $2000
#XINPUT_GAMEPAD_X                  = $4000
#XINPUT_GAMEPAD_Y                  = $8000

Structure XINPUT_NORMALIZED
   buttons.l   ;See #XINPUT_GAMEPAD_ masks.
   triggerl.f   ;0.0 to 1.0
   triggerr.f   ;0.0 to 1.0
   thumblx.f   ;Left thumbstick x-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbly.f   ;Left thumbstick y-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbrx.f   ;Right thumbstick x-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbry.f   ;Right thumbstick y-axis value, -1.0 to +1.0, 0.0 is centered.
EndStructure

Structure PBINPUT_PBINPUT
   dll.i
   version.i
   joysticks.i
   normalized.XINPUT_NORMALIZED
EndStructure

Macro Xinput_Button(buttons, mask)
   Bool(buttons & mask)
EndMacro

Procedure.i PBinput_Init()
   Protected *pbinput.PBINPUT_PBINPUT

   *pbinput = AllocateMemory(SizeOf(PBINPUT_PBINPUT))
   If *pbinput = #False
      ProcedureReturn #Null
   EndIf
   
   *pbinput\joysticks = InitJoystick()

   ProcedureReturn *pbinput
EndProcedure

Procedure.i PBinput_Free(*pbinput.PBINPUT_PBINPUT)
   If *pbinput = #Null
      ProcedureReturn #Null
   EndIf

   FreeMemory(*pbinput)

   ProcedureReturn #Null
EndProcedure

;A centering issue seems to occur on some gamepads, possible cause is that the game pad reports a signed value but
;the center is both 0 and 1 or 0 and -1, instead of just 0, after calibration the 1 or -1 may be reversed,
;due to this the value 1 and -1 are treated as being 0.
;Ideally this fix should be a user option. The fix is possibly needed for all analog inputs.
;This center issue does not seem to happen with Xinput.
Procedure.i PBinput_GetType1(*pbinput.PBINPUT_PBINPUT, index.l) ;Old gamepad mapping (analog and digital).
   Protected error.i, result.i, buttons.l
   If (index < 0) Or (index >= *pbinput\joysticks)
      ProcedureReturn #Null
   EndIf
   If ExamineJoystick(index) = #False
      ProcedureReturn #Null
   EndIf
   ZeroMemory_(*pbinput\normalized, SizeOf(XINPUT_NORMALIZED))

   If JoystickButton(index, 1)
      buttons | #XINPUT_GAMEPAD_A
   EndIf
   If JoystickButton(index, 2)
      buttons | #XINPUT_GAMEPAD_B
   EndIf
   If JoystickButton(index, 3)
      buttons | #XINPUT_GAMEPAD_X
   EndIf
   If JoystickButton(index, 4)
      buttons | #XINPUT_GAMEPAD_Y
   EndIf
   If JoystickButton(index, 5)
      buttons | #XINPUT_GAMEPAD_LEFT_SHOULDER
   EndIf
   If JoystickButton(index, 6)
      buttons | #XINPUT_GAMEPAD_RIGHT_SHOULDER
   EndIf
   If JoystickButton(index, 7) ;On the test gamepad this is Left Trigger.
      ;This is technically wrong, but no other logical way we can map this and still let it make sense to the user.
      *pbinput\normalized\triggerl = 1.0
   EndIf
   If JoystickButton(index, 8) ;On the test gamepad this is Right Trigger.
      ;This is technically wrong, but no other logical way we can map this and still let it make sense to the user.
      *pbinput\normalized\triggerr = 1.0
   EndIf
   If JoystickButton(index, 9)
      buttons | #XINPUT_GAMEPAD_BACK
   EndIf
   If JoystickButton(index, 10)
      buttons | #XINPUT_GAMEPAD_START
   EndIf
   If JoystickButton(index, 11)
      buttons | #XINPUT_GAMEPAD_LEFT_THUMB
   EndIf
   If JoystickButton(index, 12)
      buttons | #XINPUT_GAMEPAD_RIGHT_THUMB
   EndIf

   ;The D-Pad is another weird mapping that is technically wrong, but this is the only way to map it so it makes sense to the user.
   result = JoystickAxisX(index, 2, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   If result < 0
      buttons | #XINPUT_GAMEPAD_DPAD_LEFT
   EndIf
   If result > 0
      buttons | #XINPUT_GAMEPAD_DPAD_RIGHT
   EndIf
   result = JoystickAxisY(index, 2, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   If result > 0
      buttons | #XINPUT_GAMEPAD_DPAD_UP
   EndIf
   If result < 0
      buttons | #XINPUT_GAMEPAD_DPAD_DOWN
   EndIf

   *pbinput\normalized\buttons = buttons

   result = JoystickAxisZ(index, 0, #PB_Relative) ;This is the horizontal of the right thumbstick.
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumbrx = result * 0.001
   result = JoystickAxisZ(index, 1, #PB_Relative) ;This is the vertical of the right thumbstick.
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumbry = (-result) * 0.001

   result = JoystickAxisX(index, 0, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumblx = result * 0.001
   result = JoystickAxisY(index, 0, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumbly = (-result) * 0.001

   ProcedureReturn *pbinput\normalized
EndProcedure

;************************************************************************
;Example
Define *pbinput, *state.XINPUT_NORMALIZED

If OpenConsole("PBinput1 Test") = #False
   End
EndIf
EnableGraphicalConsole(#True)

*pbinput = PBinput_Init()
If *pbinput = #Null
   PrintN("PBinput_Init() failed.")
   End
EndIf

;Note that with DirectInput the Left and Right triggers are considered being on the same (Z) axis, with Xinput they are not.

; If a DirectInput gamepad is connected,
; Expected results with PBinput_GetType1() are...
; Button A: 0 or 1
; Button B: 0 or 1
; Button X: 0 or 1
; Button Y: 0 or 1
; Button Left Shoulder: 0 or 1
; Button Right Shoulder: 0 or 1
; Button Back: 0 or 1
; Button Start: 0 or 1
; Button Left Thumb: 0 or 1
; Button Right Thumb: 0 or 1
; Button D-Pad Left: 0 or 1
; Button D-Pad Up: 0 or 1
; Button D-Pad Right: 0 or 1
; Button D-Pad Down: 0 or 1
; Left Trigger: 0.0 or 1.0
; Right Trigger: 0.0 or 1.0
; Left Stick X: -1.0 (left) to 0.0 (center) to 1.0 (right)
; Left Stick Y: -1.0 (down) to 0.0 (center) to 1.0 (up)
; Right Stick X: -1.0 (left) to 0.0 (center) to 1.0 (right)
; Right Stick Y: -1.0 (down) to 0.0 (center) to 1.0 (up)

Repeat
   *state = PBinput_GetType1(*pbinput, 0)
   If *state
      ClearConsole()
      PrintN("Button A: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_A)))
      PrintN("Button B: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_B)))
      PrintN("Button X: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_X)))
      PrintN("Button Y: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_Y)))
      PrintN("Button Left Shoulder: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_LEFT_SHOULDER)))
      PrintN("Button Right Shoulder: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_RIGHT_SHOULDER)))
      PrintN("Button Back: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_BACK)))
      PrintN("Button Start: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_START)))
      PrintN("Button Left Thumb: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_LEFT_THUMB)))
      PrintN("Button Right Thumb: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_RIGHT_THUMB)))
      PrintN("Button D-Pad Left: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_LEFT)))
      PrintN("Button D-Pad Up: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_UP)))
      PrintN("Button D-Pad Right: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_RIGHT)))
      PrintN("Button D-Pad Down: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_DOWN)))
      PrintN("Left Trigger: " + StrF(*state\triggerl, 7) )
      PrintN("Right Trigger: " + StrF(*state\triggerr, 7))
      PrintN("Left Stick X: " + StrF(*state\thumblx, 7))
      PrintN("Left Stick Y: " + StrF(*state\thumbly, 7))
      PrintN("Right Stick X: " + StrF(*state\thumbrx, 7))
      PrintN("Right Stick Y: " + StrF(*state\thumbry, 7))
      PrintN("")
      PrintN("Press enter to stop.")
   Else
      PrintN("Error")
      Break
   EndIf
   Delay(100)
Until InKey() <> ""

PrintN("")
PrintN("Press enter to quit.")
Input()

*pbinput = PBinput_Free(*pbinput)
CloseConsole()
End


Code:
;pbinput2.pb
;(c) 2014 Roger Hågensen
;zlib License, http://en.wikipedia.org/wiki/Zlib_License

;This code is what you would use if the user selects "Type 2" controller mapping in your software.

EnableExplicit

#XINPUT_GAMEPAD_DPAD_UP            = $0001
#XINPUT_GAMEPAD_DPAD_DOWN         = $0002
#XINPUT_GAMEPAD_DPAD_LEFT         = $0004
#XINPUT_GAMEPAD_DPAD_RIGHT         = $0008
#XINPUT_GAMEPAD_START            = $0010
#XINPUT_GAMEPAD_BACK               = $0020
#XINPUT_GAMEPAD_LEFT_THUMB         = $0040
#XINPUT_GAMEPAD_RIGHT_THUMB      = $0080
#XINPUT_GAMEPAD_LEFT_SHOULDER      = $0100
#XINPUT_GAMEPAD_RIGHT_SHOULDER   = $0200
#XINPUT_GAMEPAD_A                  = $1000
#XINPUT_GAMEPAD_B                  = $2000
#XINPUT_GAMEPAD_X                  = $4000
#XINPUT_GAMEPAD_Y                  = $8000

Structure XINPUT_NORMALIZED
   buttons.l   ;See #XINPUT_GAMEPAD_ masks.
   triggerl.f   ;0.0 to 1.0
   triggerr.f   ;0.0 to 1.0
   thumblx.f   ;Left thumbstick x-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbly.f   ;Left thumbstick y-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbrx.f   ;Right thumbstick x-axis value, -1.0 to +1.0, 0.0 is centered.
   thumbry.f   ;Right thumbstick y-axis value, -1.0 to +1.0, 0.0 is centered.
EndStructure

Structure PBINPUT_PBINPUT
   dll.i
   version.i
   joysticks.i
   normalized.XINPUT_NORMALIZED
EndStructure

Macro Xinput_Button(buttons, mask)
   Bool(buttons & mask)
EndMacro

Procedure.i PBinput_Init()
   Protected *pbinput.PBINPUT_PBINPUT

   *pbinput = AllocateMemory(SizeOf(PBINPUT_PBINPUT))
   If *pbinput = #False
      ProcedureReturn #Null
   EndIf
   
   *pbinput\joysticks = InitJoystick()

   ProcedureReturn *pbinput
EndProcedure

Procedure.i PBinput_Free(*pbinput.PBINPUT_PBINPUT)
   If *pbinput = #Null
      ProcedureReturn #Null
   EndIf

   FreeMemory(*pbinput)

   ProcedureReturn #Null
EndProcedure

;A centering issue seems to occur on some gamepads, possible cause is that the game pad reports a signed value but
;the center is both 0 and 1 or 0 and -1, instead of just 0, after calibration the 1 or -1 may be reversed,
;due to this the value 1 and -1 are treated as being 0.
;Ideally this fix should be a user option. The fix is possibly needed for all analog inputs.
;This center issue does not seem to happen with Xinput.
Procedure.i PBinput_GetType2(*pbinput.PBINPUT_PBINPUT, index.l) ;Xinput compatible gamepad controller layout/mapping.
   Protected error.i, result.i, buttons.l
   If (index < 0) Or (index >= *pbinput\joysticks)
      ProcedureReturn #Null
   EndIf
   If ExamineJoystick(index) = #False
      ProcedureReturn #Null
   EndIf
   ZeroMemory_(*pbinput\normalized, SizeOf(XINPUT_NORMALIZED))

   If JoystickButton(index, 1)
      buttons | #XINPUT_GAMEPAD_A
   EndIf
   If JoystickButton(index, 2)
      buttons | #XINPUT_GAMEPAD_B
   EndIf
   If JoystickButton(index, 3)
      buttons | #XINPUT_GAMEPAD_X
   EndIf
   If JoystickButton(index, 4)
      buttons | #XINPUT_GAMEPAD_Y
   EndIf
   If JoystickButton(index, 5)
      buttons | #XINPUT_GAMEPAD_LEFT_SHOULDER
   EndIf
   If JoystickButton(index, 6)
      buttons | #XINPUT_GAMEPAD_RIGHT_SHOULDER
   EndIf
   If JoystickButton(index, 7)
      buttons | #XINPUT_GAMEPAD_BACK
   EndIf
   If JoystickButton(index, 8)
      buttons | #XINPUT_GAMEPAD_START
   EndIf
   If JoystickButton(index, 9)
      buttons | #XINPUT_GAMEPAD_LEFT_THUMB
   EndIf
   If JoystickButton(index, 10)
      buttons | #XINPUT_GAMEPAD_RIGHT_THUMB
   EndIf

   ;The D-Pad is another weird mapping that is technically wrong, but this is the only way to map it so it makes sense to the user.
   result = JoystickAxisX(index, 2, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   If result < 0
      buttons | #XINPUT_GAMEPAD_DPAD_LEFT
   EndIf
   If result > 0
      buttons | #XINPUT_GAMEPAD_DPAD_RIGHT
   EndIf
   result = JoystickAxisY(index, 2, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   If result > 0
      buttons | #XINPUT_GAMEPAD_DPAD_UP
   EndIf
   If result < 0
      buttons | #XINPUT_GAMEPAD_DPAD_DOWN
   EndIf

   *pbinput\normalized\buttons = buttons

   result = JoystickAxisZ(index, 0, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   If result < 0
      *pbinput\normalized\triggerr = (-result) * 0.001
   ElseIf result > 0
      *pbinput\normalized\triggerl = result * 0.001
   EndIf

   result = JoystickAxisX(index, 0, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumblx = result * 0.001
   result = JoystickAxisY(index, 0, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumbly = (-result) * 0.001

   result = JoystickAxisX(index, 1, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumbrx = result * 0.001
   result = JoystickAxisY(index, 1, #PB_Relative)
   If (result = 1) Or (result = -1) ;center fix
      result = 0
   EndIf
   *pbinput\normalized\thumbry = (-result) * 0.001

   ProcedureReturn *pbinput\normalized
EndProcedure

;************************************************************************
;Example
Define *pbinput, *state.XINPUT_NORMALIZED

If OpenConsole("PBinput2 Test") = #False
   End
EndIf
EnableGraphicalConsole(#True)

*pbinput = PBinput_Init()
If *pbinput = #Null
   PrintN("PBinput_Init() failed.")
   End
EndIf

;Note that with DirectInput the Left and Right triggers are considered being on the same (Z) axis, with Xinput they are not.

; If a Xinput gamepad is connected,
; the expected results with PBinput_Get2() are...
; Button A: 0 or 1
; Button B: 0 or 1
; Button X: 0 or 1
; Button Y: 0 or 1
; Button Left Shoulder: 0 or 1
; Button Right Shoulder: 0 or 1
; Button Back: 0 or 1
; Button Start: 0 or 1
; Button Left Thumb: 0 or 1
; Button Right Thumb: 0 or 1
; Button D-Pad Left: 0 or 1
; Button D-Pad Up: 0 or 1
; Button D-Pad Right: 0 or 1
; Button D-Pad Down: 0 or 1
; Left Trigger: 0.0 to 1.0
; Right Trigger: 0.0 to 1.0
; Left Stick X: -1.0 (left) to 0.0 (center) to 1.0 (right)
; Left Stick Y: -1.0 (down) to 0.0 (center) to 1.0 (up)
; Right Stick X: -1.0 (left) to 0.0 (center) to 1.0 (right)
; Right Stick Y: -1.0 (down) to 0.0 (center) to 1.0 (up)

Repeat
   *state = PBinput_GetType2(*pbinput, 0)
   If *state
      ClearConsole()
      PrintN("Button A: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_A)))
      PrintN("Button B: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_B)))
      PrintN("Button X: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_X)))
      PrintN("Button Y: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_Y)))
      PrintN("Button Left Shoulder: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_LEFT_SHOULDER)))
      PrintN("Button Right Shoulder: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_RIGHT_SHOULDER)))
      PrintN("Button Back: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_BACK)))
      PrintN("Button Start: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_START)))
      PrintN("Button Left Thumb: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_LEFT_THUMB)))
      PrintN("Button Right Thumb: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_RIGHT_THUMB)))
      PrintN("Button D-Pad Left: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_LEFT)))
      PrintN("Button D-Pad Up: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_UP)))
      PrintN("Button D-Pad Right: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_RIGHT)))
      PrintN("Button D-Pad Down: " + Str(Xinput_Button(*state\buttons, #XINPUT_GAMEPAD_DPAD_DOWN)))
      PrintN("Left Trigger: " + StrF(*state\triggerl, 7) )
      PrintN("Right Trigger: " + StrF(*state\triggerr, 7))
      PrintN("Left Stick X: " + StrF(*state\thumblx, 7))
      PrintN("Left Stick Y: " + StrF(*state\thumbly, 7))
      PrintN("Right Stick X: " + StrF(*state\thumbrx, 7))
      PrintN("Right Stick Y: " + StrF(*state\thumbry, 7))
      PrintN("")
      PrintN("Press enter to stop.")
   Else
      PrintN("Error")
      Break
   EndIf
   Delay(100)
Until InKey() <> ""

PrintN("")
PrintN("Press enter to quit.")
Input()

*pbinput = PBinput_Free(*pbinput)
CloseConsole()
End


Last edited by Rescator on Fri Nov 07, 2014 12:29 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
 Post subject: Re: Gamepad
PostPosted: Fri Oct 31, 2014 2:43 am 
Offline
Addict
Addict
User avatar

Joined: Sat Feb 19, 2005 5:05 pm
Posts: 1769
Location: Norway
Edited the code a little to add a quick fix.

A centering issue seems to occur on some gamepads, possible cause is that the game pad reports a signed value but the center is both 0 and 1 or 0 and -1, instead of just 0, after calibration the 1 or -1 may be reversed, due to this the value 1 and -1 are treated as being 0.

Ideally this fix should be a user option. The fix is possibly needed for all analog inputs.

This center issue does not seem to happen with Xinput.


Top
 Profile  
Reply with quote  
 Post subject: Re: Gamepad (Windows 2000/XP/Vista/7/8/10)
PostPosted: Tue Dec 02, 2014 11:45 pm 
Offline
User
User

Joined: Mon Feb 21, 2011 12:15 pm
Posts: 25
This is very cool! Tried it out with my Logitech gamepad (xinput) and it's working great. Thanks for sharing your code.


Top
 Profile  
Reply with quote  
 Post subject: Re: Gamepad (Windows 2000/XP/Vista/7/8/10)
PostPosted: Sun Jan 18, 2015 12:02 am 
Offline
User
User

Joined: Wed Aug 06, 2008 8:21 am
Posts: 71
:D Thanks for share! :D

_________________
PureBASIC v5.41 LTS , Windows v8.1 x64
Forget UNICODE - Keep it BASIC !


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 9 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye