How does one simply detect left/right XBox Controller thumbstick movement?

So I have searched around on the Wiki and other forums
how to detect in which direction the player is pushing one of two thumbsticks on a XBox controller.
I was unfortunately unable to find the right information.

So currently I am trying to write my own input/camera/movement module to replace Roblox’ build in/default.

The modules are supposed to make mouselock easier to control, better TPS, easier to detect input from any device by converting it all to 1 the same button,
for example, pressing the Y button on a XBox controller would be the same as pressing Q or E on a keyboard.
If the UserInputService ever gets an update I only need to update the module and none of my tools will break, ever.

Back to the point, I think I already know how to do the mouse part and the WASD movement, but maybe I can get some tips and hints on that as well?
Like how would I convert delta to camera angles and such without overshooting it or making it too sensitive?
And I’m stuck (and don’t even know where to begin) at the gamepad thumbstick part.
I want to detect in which direction XBox users are pressing the L/R thumbstick.
Left thumbstick for movement and right thumbstick for looking around.

Any help is really appreciated, can’t wait until I have those modules done and can start using it across games, I made it especially to make everything easier, inputservice sometimes is a bit of a hassle to me so I make modules with bindables so it’s easier to use.

8 Likes
local UserInputService = game:GetService("UserInputService")

local function OnInputChanged(Input, GameProcessedEvent)
    if Input.UserInputType == Enum.UserInputType.Gamepad1 then
        if Input.Delta.X > 0 then
            print("Right")
        elseif Input.Delta.X < 0 then
            print("Left")
        end
    end
end

UserInputService.InputChanged:Connect(OnInputChanged)

Disclaimer: I’ve never coded for a XBox controller or ever seen one so I am confident that my code may be wrong :rofl: but it seems logically correct.

Edit:
Actully I may be completely wrong
image
The op wants to distinguish between the left stick and right stick.

3 Likes

You almost had it right. This code is correct except you need to use InputChanged. Since interacting with a joystick changes how it’s being interacted with and is not considered the beginning of an input.

This code will work perfectly if you changed it to InputChanged.

3 Likes

I have tested this code with my xbox controller, however it prints both joysticks.
It does not seperate the two joysticks.

  0, -0.601580858, 0 -1, 0.320322275, 0
  Left
  0, -0.320322275, 0 -1, -0, 0
  Left
  0, 0.210943937, 0 -1, 0.210943937, 0
  Left
  0, 0.460951567, 0 -1, 0.671895504, 0
  Left
  0, 0.328104496, 0 -1, 1, 0
  Left
  0, -1, 0 -1, -1, 0
  0.664052248, 0, 0 -0.335947752, -1, 0
  0.335947752, 0, 0 0, -1, 0
  0.257820368, 0, 0 0.257820368, -1, 0
  0.476546526, 0, 0 -0.523453474, 1, 0
  Right
  0.742179632, 0, 0 1, -1, 0
  0.49220252, 0, 0 -0.0312509537, 1, 0
  0.0312509537, 0, 0 0, 1, 0
  Right
  0, 0.414044619, 0 1, -0.585955381, 0
  Right
  0, 0.585955381, 0 1, -0, 0
  0.125003815, 0, 0 0.125003815, 1, 0
  Right
  0.874996185, 0, 0 1, 1, 0
  Right
  0, 0.507827997, 0 1, 0.507827997, 0
  Right
  0, 0.460951567, 0 1, 0.968779564, 0
  Right
  0, 0.0312204361, 0 1, 1, 0
  -0.421857357, 0, 0 0.578142643, 1, 0
  -0.484389782, 0, 0 0.093752861, 1, 0
  Right
  0, -0.195287943, 0 1, 0.804712057, 0
  -0.093752861, 0, 0 0, 1, 0
  Right
  0, -0.484389782, 0 1, 0.320322275, 0
  Right
  0, -0.320322275, 0 1, -0, 0
  -0.218726158, 0, 0 0.781273842, -0, 0
  -0.335947752, 0, 0 0.44532609, -0, 0
  0, -0.679708242, 0 0, -0.679708242, 0
  0, -0.320291758, 0 0, -1, 0
  0.507827997, 0, 0 0.507827997, -1, 0
  Right
  0.492172003, 0, 0 1, -1, 0
  Right
  0, 0.718741417, 0 1, -0.281258583, 0
  Right
  0, 0.281258583, 0 1, -0, 0
  Right
  0, 0.695333719, 0 1, 0.695333719, 0
  Right
  0, 0.304666281, 0 1, 1, 0
  -1, 0, 0 0, 1, 0
  0, -0.0780968666, 0 0, 0.921903133, 0

Here is the output.

2 Likes

I just did more research and it turns out there’s an enum key code for two thumbsticks, however i’m not sure which thumbstick is which as it’s not stated in the devhub.

local UserInputService = game:GetService("UserInputService")

local function OnInputChanged(Input, GameProcessedEvent)
    if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
        print("Thumbstick 1")
    elseif Input.KeyCode == Enum.KeyCode.Thumbstick2 then
        print("Thumbstick 2")
    end
end

UserInputService.InputChanged:Connect(OnInputChanged)
7 Likes

They keycodes is for when you press on the thumbsticks.
The thumbsticks can’t only be moved but also be pressed down into the controller (used for changing camera mode/quick melee attack or crouching/proning in some games).

1 Like

Does anything get printed when you press either thumbstick?

1 Like

If you press them thumbsticks is just prints that you pressed them but it does not print the movement direction.

Not true, the InputObject.KeyCode is Thumbstick1 for the left and Thumbstick2 for the right in the InputChanged event. This doesn’t apply to just pressing it. I have tested the given code by @wevetments myself on a controller and it works as expected with the proper KeyCode being given.

3 Likes
local UserInputService = game:GetService("UserInputService")

local function OnInputChanged(Input, GameProcessedEvent)
    if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
        print("Thumbstick 1")
        if Input.Delta.X > 0 then
            print("Right")
        elseif Input.Delta.X < 0 then
            print("Left")
        end
    elseif Input.KeyCode == Enum.KeyCode.Thumbstick2 then
        print("Thumbstick 2")
        if Input.Delta.X > 0 then
            print("Right")
        elseif Input.Delta.X < 0 then
            print("Left")
        end
    end
end

UserInputService.InputChanged:Connect(OnInputChanged)

I believe this should tell between the two thumbsticks and the direction they are moved at

4 Likes

So I now managed to detect both joysticks.

 Left 0, 0.804712057, 0 -1, -0.171880245, 0
  Left 0, 0.171880245, 0 -1, -0, 0
  Left 0, 0.0703146458, 0 -1, 0.0703146458, 0
  Left 0, 0.109378338, 0 -1, 0.179692984, 0
  Left 0, 0.0312509537, 0 -1, 0.210943937, 0
  Left 0, -0.00781273842, 0 -1, 0.203131199, 0
  Left 0, -0.187505722, 0 -1, 0.0156254768, 0
  Left 0.55467391, 0, 0 -0.44532609, 0.0156254768, 0
  Left 0, -0.0156254768, 0 -0.44532609, -0, 0
  Right 0, 0.210943937, 0 0, 0.210943937, 0
  Right 0, 0.789056063, 0 0, 1, 0
  Right -0.781273842, 0, 0 -0.781273842, 1, 0
  Right -0.218726158, 0, 0 -1, 1, 0
  Right 0, -1, 0 -1, -0, 0

I rotated both thumbsticks 360 degrees and made a script check for the keycode.

local UserInputService = game:GetService("UserInputService")

local function OnInputChanged(Input, GameProcessedEvent)
    if Input.UserInputType == Enum.UserInputType.Gamepad1 then
		if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
			print("Left", Input.Delta, Input.Position)
		elseif Input.KeyCode == Enum.KeyCode.Thumbstick2 then
			print("Right", Input.Delta, Input.Position)
		end
    end
end

UserInputService.InputChanged:Connect(OnInputChanged)

So this code seems functional.

BUT it appears there is also a Z axis for controller that apparently show how fast/far you pressed one of the triggers on the controller, now I am curious what the keycode/detection to that is.

7 Likes

The KeyCode for the left and right triggers would be Enum.KeyCode.R2 and Enum.KeyCode.L2
R2 for Right and L2 for Left

local UserInputService = game:GetService("UserInputService")

local function OnInputChanged(Input, GameProcessedEvent)
    if Input.UserInputType == Enum.UserInputType.Gamepad1 then
		if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
			print("Left", Input.Delta, Input.Position)
		elseif Input.KeyCode == Enum.KeyCode.Thumbstick2 then
			print("Right", Input.Delta, Input.Position)
        elseif Input.KeyCode == Enum.KeyCode.R2 then
            print("Right Trigger", Input.Position.Z)
        elseif Input.KeyCode == Enum.KeyCode.L2 then
            print("Left Trigger", Input.Position.Z)
		end
    end
end

UserInputService.InputChanged:Connect(OnInputChanged)
3 Likes

I was able to detect and seperate the joystsick input, however my next problem now is that it doesn’t reset back to 0, 0, 0.

local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")

local sc = script.Parent.smartcontrol
local keys = script.Parent.smartcontrol.keys
local sets = script.Parent.smartcontrol.settings

local filterstring = "Enum.KeyCode."
local gsub = string.gsub

local uits = {
	mousemove = Enum.UserInputType.MouseMovement;
	mouse1 = Enum.UserInputType.MouseButton1;
	mouse2 = Enum.UserInputType.MouseButton2;
	mouse3 = Enum.UserInputType.MouseButton3;
	mousewheel = Enum.UserInputType.MouseWheel;
	
	gamepad = Enum.UserInputType.Gamepad1;
}

local v3 = Vector3.new
--local lasttick = tick()





con.main = function() --Start of main function.
print("Main module starting...")

sets.mousesensitivity.Value = uis.MouseDeltaSensitivity


--Bindables--
sets.controlrequest.OnInvoke = function(mode, value)
	if mode == "get" then
		return controls
	elseif mode == "set" then
		controls = value
	end
end



--variable change events--
sets.mouselocked.Changed:Connect(function()
	if sets.mouselocked.Value then
		uis.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		uis.MouseBehavior = Enum.MouseBehavior.Default
	end
end)

sets.mousesensitivity.Changed:Connect(function()
	uis.MouseDeltaSensitivity = sets.mousesensitivity / 100
end)





--input events--
uis.InputBegan:Connect(function(input, proc)
	--if proc then return end
	local keycode = gsub(tostring(input.KeyCode), filterstring, "")
	local _keycode = controls[keycode]
	
	if _keycode then
		sc.inputstart:Fire(_keycode, not proc)
		if proc then return end
		keys[_keycode].Value = true
	end
	
	if input.UserInputType == uits.mouse1 then
		sc.inputstart:Fire("mouse1", not proc)
		if proc then return end
		keys.mouse1.Value = true
		
	elseif input.UserInputType == uits.mouse2 then
		sc.inputstart:Fire("mouse2", not proc)
		if proc then return end
		keys.mouse2.Value = true
		
	elseif input.UserInputType == uits.mouse3 then
		sc.inputstart:Fire("mouse3", not proc)
		if proc then return end
		keys.mouse3.Value = true
		
	elseif input.UserInputType == uits.mousewheel then
		sc.wheel:Fire(input.Position.Z, not proc)
	end
end)


uis.InputEnded:Connect(function(input, proc)
	--if proc then return end
	local keycode = gsub(tostring(input.KeyCode), filterstring, "")
	local _keycode = controls[keycode]
	
	if _keycode then
		sc.inputstart:Fire(_keycode, not proc)
		if proc then return end
		keys[_keycode].Value = false
	end
	
	if input.UserInputType == uits.mouse1 then
		sc.inputstart:Fire("mouse1", not proc)
		if proc then return end
		keys.mouse1.Value = false
		
	elseif input.UserInputType == uits.mouse2 then
		sc.inputstart:Fire("mouse2", not proc)
		if proc then return end
		keys.mouse2.Value = false
		
	elseif input.UserInputType == uits.mouse3 then
		sc.inputstart:Fire("mouse3", not proc)
		if proc then return end
		keys.mouse3.Value = false
	end
	
	--Thumbstick--
	if input.UserInputType == uits.gamepad then
		if _keycode == "Thumbstick1" then
			sc.stick1.Value = v3()
		elseif _keycode == "Thumbstick2" then
			sc.stick2.Value = v3()
		end
	end
	
end)


uis.InputChanged:Connect(function(input, proc)
	if input.UserInputType == uits.mousemove then
		sc.stick2.move:Fire(input.Position, input.Delta, not proc)
		
	elseif input.UserInputType == uits.gamepad then
		if input.KeyCode == Enum.KeyCode.Thumbstick1 then
			sc.stick1.move:Fire(input.Position, input.Delta, not proc)
			if not proc then
				sc.stick1.Value = input.Position
			end
			
		elseif input.KeyCode == Enum.KeyCode.Thumbstick2 then
			sc.stick2.move:Fire(input.Position, input.Delta, not proc)
			if not proc then
				sc.stick2.Value = input.Position
			end
		end
		
	end
end)

Edit: I already fixed it, I noticed I did if _keycode == “Thumbstick1” then which won’t work since it’s not in the table.

Edit2: However I would still like some tips/hints on my module.

1 Like

I’m unsure of this, but probably when InputEnded is fired, it’s safe to assume that the position of either joystick would be 0

UserInputService.InputEnded:Connect(function(Input, GPE)
    if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
        print("Left", Input.Delta.X)
    end
end
2 Likes

InputEnded returned values like 0.1859394, -0.05839, 0.
Which is not entirely 0, 0, 0.

Edit: I fixed this by forcing the values to be 0, 0, 0 after input ended.

For future reference, it is common and recommended to set a dead zone for joysticks. Roblox by default uses 0.2 for their movement scripts. The most important is this helps prevent jitter that controllers may end up doing.

Taken from the Roblox movement script they do this to check inputObject.Position.magnitude > thumbstickDeadzone

3 Likes

Do controllers jitter then?
I didn’t really know that was a thing tbh.

Now I came upon the next error.

local mod = {}

local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")

local sc = script.Parent.smartcontrol
local keys = script.Parent.smartcontrol.keys
local sets = script.Parent.smartcontrol.settings

local stick = script.Parent.smartcontrol.stick1

local player = game:GetService("Players").LocalPlayer

local v3 = Vector3.new
local stickpos = v3()


mod.main = function()
	
	uis.InputChanged:Connect(function(input, proc)
		local keycode = input.KeyCode
--		if proc then return end
		if input.UserInputType == Enum.UserInputType.Gamepad1 then
			if keycode == Enum.KeyCode.Thumbstick1 then
				stick.move:Fire(input.Position, input.Delta, not proc)
				if not proc then
					stickpos = input.Position
					stick.Value = stickpos
				end
			end
			
		elseif input.UserInputType == Enum.UserInputType.Keyboard then
			if keycode == Enum.KeyCode.W and not proc then
				stickpos = stickpos + v3(0, 1, 0)
			elseif keycode == Enum.keyCode.S and not proc then
				stickpos = stickpos + v3(0, -1, 0)
			elseif keycode == Enum.keyCode.A and not proc then
				stickpos = stickpos + v3(0, -1, 0)
			elseif keycode == Enum.keyCode.D and not proc then
				stickpos = stickpos + v3(0, 1, 0)
			end
			if not proc then stick.Value = stickpos end
			stick.move:Fire(input.Position, input.Delta, not proc)
		end
		
	end) --End function
	
	uis.InputEnded:Connect(function(input, proc)
		local keycode = input.KeyCode
		
		if input.UserInputType == Enum.UserInputType.Gamepad1 then
			if keycode == Enum.KeyCode.Thumbstick1 then
				stick.move:Fire(input.Position, input.Delta, not proc)
				if not proc then
					stickpos = v3()
					stick.Value = stickpos
				end
			end
			
		elseif input.UserInputType == Enum.UserInputType.Keyboard then
			if keycode == Enum.KeyCode.W and not proc then
				stickpos = stickpos + v3(0, -1, 0)
			elseif keycode == Enum.keyCode.S and not proc then
				stickpos = stickpos + v3(0, 1, 0)
			elseif keycode == Enum.keyCode.A and not proc then
				stickpos = stickpos + v3(0, 1, 0)
			elseif keycode == Enum.keyCode.D and not proc then
				stickpos = stickpos + v3(0, -1, 0)
			end
			if not proc then stick.Value = stickpos end
			stick.move:Fire(input.Position, input.Delta, not proc)
		end
		
	end) --End function
	
end

return mod

Output:

  17:57:49.121 - keyCode is not a valid EnumItem
17:57:49.122 - Stack Begin
17:57:49.122 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:49.123 - Stack End
  17:57:49.355 - keyCode is not a valid EnumItem
17:57:49.356 - Stack Begin
17:57:49.356 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:49.357 - Stack End
  17:57:49.753 - keyCode is not a valid EnumItem
17:57:49.755 - Stack Begin
17:57:49.755 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:49.756 - Stack End
  17:57:49.836 - keyCode is not a valid EnumItem
17:57:49.837 - Stack Begin
17:57:49.838 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:49.838 - Stack End
  17:57:49.939 - keyCode is not a valid EnumItem
17:57:49.940 - Stack Begin
17:57:49.940 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:49.941 - Stack End
  17:57:50.571 - keyCode is not a valid EnumItem
17:57:50.572 - Stack Begin
17:57:50.572 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:50.573 - Stack End
  17:57:50.952 - keyCode is not a valid EnumItem
17:57:50.954 - Stack Begin
17:57:50.954 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:50.955 - Stack End
  17:57:51.021 - keyCode is not a valid EnumItem
17:57:51.022 - Stack Begin
17:57:51.022 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:51.023 - Stack End
  17:57:51.205 - keyCode is not a valid EnumItem
17:57:51.205 - Stack Begin
17:57:51.206 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:51.206 - Stack End
  17:57:52.123 - keyCode is not a valid EnumItem
17:57:52.124 - Stack Begin
17:57:52.124 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:52.125 - Stack End
  17:57:52.439 - keyCode is not a valid EnumItem
17:57:52.440 - Stack Begin
17:57:52.440 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:52.441 - Stack End
  17:57:52.704 - keyCode is not a valid EnumItem
17:57:52.704 - Stack Begin
17:57:52.705 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:52.705 - Stack End
  17:57:53.005 - keyCode is not a valid EnumItem
17:57:53.005 - Stack Begin
17:57:53.005 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:53.006 - Stack End
  17:57:53.620 - keyCode is not a valid EnumItem
17:57:53.621 - Stack Begin
17:57:53.621 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:53.622 - Stack End
  17:57:53.655 - keyCode is not a valid EnumItem
17:57:53.656 - Stack Begin
17:57:53.656 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:53.656 - Stack End
  17:57:54.105 - keyCode is not a valid EnumItem
17:57:54.106 - Stack Begin
17:57:54.106 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:54.107 - Stack End
  17:57:54.255 - keyCode is not a valid EnumItem
17:57:54.256 - Stack Begin
17:57:54.256 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:54.257 - Stack End
  17:57:54.387 - keyCode is not a valid EnumItem
17:57:54.388 - Stack Begin
17:57:54.389 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:54.389 - Stack End
  17:57:54.738 - keyCode is not a valid EnumItem
17:57:54.739 - Stack Begin
17:57:54.739 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:54.740 - Stack End
  17:57:54.888 - keyCode is not a valid EnumItem
17:57:54.889 - Stack Begin
17:57:54.889 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:54.890 - Stack End
  17:57:55.187 - keyCode is not a valid EnumItem
17:57:55.188 - Stack Begin
17:57:55.188 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:55.189 - Stack End
  17:57:55.370 - keyCode is not a valid EnumItem
17:57:55.371 - Stack Begin
17:57:55.372 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:55.372 - Stack End
  17:57:56.855 - keyCode is not a valid EnumItem
17:57:56.856 - Stack Begin
17:57:56.856 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:56.856 - Stack End
  17:57:57.021 - keyCode is not a valid EnumItem
17:57:57.022 - Stack Begin
17:57:57.022 - Script 'Players.RubyEpicFox.PlayerScripts.smartcontrol.movement', Line 63
17:57:57.022 - Stack End

I am not entirely sure what the errors are telling me, all I did was make a reference to a keycode to make it quicker.

This happened when I tried to press WASD to set the Vector3 of one of the values.

“keyCode” is supposed to be “KeyCode”
The capitalisation of “K” is important as Enum items are case sensitive

Darn, I have noticed.
I don’t know how I could’ve missed that.

I think i fixed that now, needed to go through all modules to make sure I didn’t make that same mistake there.

I’m still gonna experiment and see if I can get camera and movement to work the way as intended.

1 Like