Help me detect Joystick for Mobile and Console for Throttle and Steer

Hi all! I’m relaunching a game tomorrow where you ride a Marble down a lengthy track. Part of the relaunch includes controlling your marble but I’m totally stuck on how to get anything more than WASD. Looking for any help, even code to solve this. Wiki isn’t giving me enough info as a mostly non-programmer.
I know I need Joystick detecting and reading the X,Y. I don’t know how to handle it on both mobile and gamepad.

Here’s the existing important code:

local sp = script.Parent
local push_factor = 30 -- How much force is applied to the ball to make it move.
local drainrate = 1 -- How much power to drain.
local uis = game:GetService("UserInputService")

local power_level = script.Parent:WaitForChild("Frame"):WaitForChild("amount")
local max_power = power_level.MaxValue
local powerbar = script.Parent.Frame:WaitForChild("powerbar")
local powerbar_bar = powerbar:WaitForChild("power")
local marble = game.Players.LocalPlayer.Character:WaitForChild("Marble")
local camera = game.Workspace.CurrentCamera

local throttle = 0
local steer = 0

local is_moving = false
local forward = false
local prompted_power = false
local riding = true
refilling = false



function move()
	wait(0.05)
	if is_moving and power_level.Value > 0 and refilling == false then
		
		if script.Parent.Moving.IsPlaying == false then
			script.Parent.Moving:Play()
		end
		
		power_level.Value = power_level.Value - drainrate
		print(power_level.Value)
		powerbar_bar:TweenSize(UDim2.new((power_level.Value/100),0,0.35,0),"Out","Linear",0.1,true)
		--print(power_level.Value/max_power)
		if forward == false then
			bf.force = camera.CoordinateFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor)
		else
			bf.force = camera.CoordinateFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor/2)
		end
		move()
	elseif power_level.Value <= 0 and refilling == false then
		refilling = true
		script.Parent.Controls.Reloading.Visible = true
		script.Parent.Reload:Play()
		script.Parent.Moving:Stop()
		for refill = 1, 200 do
			powerbar_bar:TweenSize(UDim2.new((power_level.Value/100),0,0.35,0),"Out","Linear",0.1,true)
			power_level.Value = power_level.Value + .5
			wait(.1)
		end
		refilling = false
		script.Parent.Controls.Reloading.Visible = false
	end
		
end


uis.InputBegan:connect(function(input)
	if input.KeyCode == Enum.KeyCode.W then
		throttle = -1
		if not is_moving then
			is_moving = true
			--forward = true
			move()
		end
	elseif input.KeyCode == Enum.KeyCode.S then
		throttle = 1
		--forward = true
		if not is_moving then
			is_moving = true
			move()
		end
	elseif input.KeyCode == Enum.KeyCode.A then
		steer = -1
		--forward = false
		if not is_moving then
			is_moving = true
			move()
		end
	elseif input.KeyCode == Enum.KeyCode.D then
		steer = 1
		--forward = false
		if not is_moving then
			is_moving = true
			move()
		end
	end
end)


uis.InputEnded:connect(function(input)
	if input.KeyCode == Enum.KeyCode.W or input.KeyCode == Enum.KeyCode.S then
		throttle = 0
		forward = false
	elseif input.KeyCode == Enum.KeyCode.A or input.KeyCode == Enum.KeyCode.D then
		steer = 0
	end
	if steer == 0 and throttle == 0 then
		bf.force = Vector3.new()
		is_moving = false
		script.Parent.Moving:Stop()
	end
end)

Again, appreciate any and all help. I’ve been at this for just too long and I’m giving up!

10 Likes

Have you tried using a vehicle seat yet? It can detect movement and its way easier to understand

1 Like

To add additional context, I tried the below code for Gamepads but it was draining the power amount almost instantly, I think it threw move() into a loop which confuses me because WASD doesn’t throw move() into a loop.

	
	uis.InputChanged:Connect(function(Input)
		if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
			
			local joystickX = Input.Position.X
			local joystickY = Input.Position.Y
			--print("X"..joystickX, "Y"..joystickY)

			if joystickX > 0.5 then
				steer = 1
				forward = true
				if not is_moving then
					is_moving = true
					move()
				end
			elseif joystickX < -0.5 then
				steer = -1
				forward = true
				if not is_moving then
					is_moving = true
					move()
				end
			else
				steer = 0
				is_moving = false
			end

			if joystickY > 0.5 then
				throttle = 1
				if not is_moving then
					is_moving = true
					move()
				end
			elseif joystickY < -0.5 then
				throttle = -1
				if not is_moving then
					is_moving = true
					move()
				end
			else
				throttle = 0
				is_moving = false
			end		
			
			if steer == 0 and throttle == 0 then
				bf.force = Vector3.new()
				is_moving = false
			end
			
		end
	end)
	

(This above code doesn’t solve for mobile which is a problem I don’t know where to start. It’s Gamepad only.)

2 Likes

If you are using a vehicle seat try doing something like:

while VehicleSeat.Throttle == 1 do

end

1 Like

Have you tried using these?

Mobile: Best way to read mobile thumbstick input? - #3 by V_ChampionSSR

Xbox: How to accurately determine which direction an Xbox thumbstick is pointing?

1 Like

Try this method we are requiring the core Controller Module. GetMoveVector returns the current direction of movement.

local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer
local ControlModule = require(LocalPlayer.PlayerScripts:WaitForChild("PlayerModule"):WaitForChild("ControlModule"))

while true  do
	task.wait()
	local currentDirection = ControlModule:GetMoveVector()
	print(currentDirection)
end
3 Likes

I think the most standard way, as long as humanoid is present, is to simply read Humanoid.MoveDirection every frame, which the value is set by the default character controller. So, the body force’s force would be as simple as

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local me = Players.LocalPlayer
local character = me.Character or me.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")

local bodyforce = -- path.to.BodyForce

local push_factor = 30
local drain_rate = 1

local con = RunService.Stepped:Connect(function(delta_time)
    power_level.Value -= drain_rate * delta_time
    body_force.Force = humanoid.MoveDirection * push_factor
end)

humanoid.Died:Connect(function()
	con:Disconnect()
end)

The orientation of character, WalkSpeed, and even the fact that PlatformStand / Sit is set or not (so that physics can take control) of humanoid does not affect which plane the moveVector will be on (it will always be on X Z plane) and will always have magnitude of 1 when input is made, or 0 when no inputs are made, making it an ideal property to read to determine which direction the player wants to move. To add on, all WASD, Gamepad input, and mobile inputs are applied here so you won’t have to have different reading method per device!

You could also fork the PlayerModule as the player above suggests (reading from the injected PlayerModule does not work if I recall, you have to have a fork the module and disable default injection), but tbh I think its better to just read from Humanoid as if ever there is any update to PlayerModule, you will have to manually fork the new PlayerModule, apply same edits, etc, whereas with Humanoid any updates will be auto-reflected.

5 Likes

This is coming from a novice programmer, so take anything I say with a grain of salt. That said, I’m fairly certain move() is being thrown into a loop as InputChanged runs every time that the JoyStick position changes at all, meaning that even just going straight forward can run 40+ times with half of them being greater than 0.5. I’ve edited the code below a bit to hopefully work around that, by checking if the position matches what it was previously (and resetting when it’s put back to rest so legit repeat inputs work).

-- Hold the current Vertical and Horizontal Direction
local vertDir = 0
local horzDir = 0

uis.InputChanged:Connect(function(Input)
	if Input.KeyCode == Enum.KeyCode.Thumbstick1 then


		-- Check if the Joystick has been put back to rest
		if joystickX < 0.5 and joystickX > -0.5 then
			horzDir = 0
		end
		
		if joystickY < 0.5 and joystickY > -0.5 then
			vertDir = 0
		end

		local joystickX = Input.Position.X
		local joystickY = Input.Position.Y
		--print("X"..joystickX, "Y"..joystickY)

		-- Check Horizontal Direction
		if joystickX > 0.5 and horzDir ~= 1 then
			steer = 1
			forward = true
			horzDir = 1
			if not is_moving then
				is_moving = true
				move()
			end
		elseif joystickX < -0.5 and horzDir ~= 2 then
			steer = -1
			forward = true
			horzDir = 2
			if not is_moving then
				is_moving = true
				move()
			end
		else
			steer = 0
			is_moving = false
		end

		-- Check Vertical Direction
		if joystickY > 0.5  and vertDir ~= 1 then
			throttle = 1
			vertDir = 1
			if not is_moving then
				is_moving = true
				move()
			end
		elseif joystickY < -0.5 and vertDir ~= 2 then
			throttle = -1
			vertDir = 2
			if not is_moving then
				is_moving = true
				move()
			end
		else
			throttle = 0
			is_moving = false
		end		

	end
end)

I don’t know how much help this could be used for, but for the latest game I worked on, I used the UserInputService’s TouchMoved. It allows you to see if the player is moving while touching and tells you the change in direction.

What you could do with this is if there is some sort of change, check if the player is interacting with the joystick, take the input.delta as seen in the example, and apply the changes to the object.

Again I don’t know if this is the best way, but I used it for Holiday Frenzy for the building toys on mobile!

1 Like

Apart from this not being the best method by far, this is also highly unreliable because said module gets updated by Roblox quite often which could literally mean the code can break at any given moment and since asimo isn’t a scripter it’ll 100% take him a while to figure out why it broke out of nowhere.

Thank you so much, and thank you to all here who offered ideas and code.

I woke up this morning to tackle this problem again and after about an hour of moving code around and trying things out, I found my solution. Using Movedirection is a great solution for figuring out what the player is doing and definitely what I’ll be doing moving forward for future games.
So others can possibly learn from this, this is what the (working) code now looks like. I’ve included my commented out force calculation mistakes.

local con = RunService.Stepped:Connect(function(delta_time)
	--local con = RunService.Heartbeat:Connect(function()
	if humanoid.MoveDirection.X > 0 then 
		steer = 1
	elseif humanoid.MoveDirection.X < 0 then
		steer = -1
	else
		steer = 0
	end
	
	if humanoid.MoveDirection.Z > 0 then 
		throttle = 1
	elseif humanoid.MoveDirection.Z < 0 then
		throttle = -1
	else
		throttle = 0
	end
	
	--print(humanoid.MoveDirection)
	bodyforce.Force = Vector3.new(0,0,0)
	if throttle ~= 0 or steer ~= 0 then
		if script.Parent.Controls.Reloading.Visible == false and power_level.Value > 0 then
			--bodyforce.Force = camera.CoordinateFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor)
			--bodyforce.Force = character.PrimaryPart.CFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor)
			if script.Parent.Moving.IsPlaying == false then
				script.Parent.Moving:Play()
			end
			bodyforce.Force= humanoid.MoveDirection*marble:GetMass()*push_factor
			power_level.Value = power_level.Value - drain_rate 
			powerbar_bar:TweenSize(UDim2.new((power_level.Value/100)/3,0,0.35,0),"Out","Linear",0.1,true)
			--print(power_level.Value)
		end
	end
	
	if throttle == 0 and steer == 0 then
		script.Parent.Moving:Stop()
	end
	
	if power_level.Value <= 0 then
		
		script.Parent.Controls.Reloading.Visible = true
		script.Parent.Reload:Play()
		script.Parent.Moving:Stop()
		for refill = 1, 300 do
			bodyforce.Force = Vector3.new(0,0,0)
			powerbar_bar:TweenSize(UDim2.new((power_level.Value/100)/3,0,0.35,0),"Out","Linear",0.1,true)
			power_level.Value = power_level.Value + 1
			wait()
		end
		script.Parent.Controls.Reloading.Visible = false
		
	end

end)

humanoid.Died:Connect(function()
	con:Disconnect()
end)

Thanks again!

3 Likes

asimo im playing Marble Rider Space And OMG!

This game is a Masterpice!

11/10

This is probably the way you should be accessing the mobile thumbstick imo, and you don’t have to go via the always questionable Humanoid:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.