Page 1 of 1

Gamepad (Windows 2000/XP/Vista/7/8/10)

Posted: Thu Oct 30, 2014 5:38 am
by Rescator
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: Select all

;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: Select all

;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: Select all

;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

Re: Gamepad

Posted: Fri Oct 31, 2014 2:43 am
by Rescator
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.

Re: Gamepad (Windows 2000/XP/Vista/7/8/10)

Posted: Tue Dec 02, 2014 11:45 pm
by missile69
This is very cool! Tried it out with my Logitech gamepad (xinput) and it's working great. Thanks for sharing your code.

Re: Gamepad (Windows 2000/XP/Vista/7/8/10)

Posted: Sun Jan 18, 2015 12:02 am
by marroh
:D Thanks for share! :D