Trouble with the mobile controls of my movement system

Hey, I have a sort of a complicated question. I have this movement system for an upcoming marble game. Currently it works perfectly with WASD. Here is the code for context of the upcoming paragraph:

Marble Movement Code
local uis = game:GetService("UserInputService")
local cas = game:GetService("ContextActionService")
local gui = game:GetService("StarterGui")
local rs = game:GetService("RunService")
local forwards, left, right, back = false, false, false, false

local Thumbstick = require(script.PlayerModule.ControlModule.TouchThumbstick)
local thumbframe = Thumbstick.new()
thumbframe:Create(game.Players.LocalPlayer.PlayerGui:WaitForChild("GameUI").TouchFrame.Zone)

cas:UnbindAction("moveForwardAction")

local plr = game.Players.LocalPlayer

local forwardsens = 2.4
local rightsens = 2.4
local leftsens = -2.4
local backsens = -2.4

local MouseInput = {
	Enum.UserInputType.MouseButton1,
	Enum.UserInputType.MouseButton2,
	Enum.UserInputType.MouseButton3,
	Enum.UserInputType.MouseMovement,
	Enum.UserInputType.MouseWheel	
}

local function CheckForMouseInput(InputType)
	for _,mouse in pairs(MouseInput) do
		if InputType == mouse then
			return true
		end
	end

	return false
end

local function CharacterAdded(chr)
	local cam = workspace.CurrentCamera
	local multiplier = Vector3.new(0,0,0)
	local hum = chr:WaitForChild("HumanoidRootPart")


	local steadypart = Instance.new("Part")
	steadypart.Parent = chr
	steadypart.CFrame = hum.CFrame
	steadypart.Massless = true
	steadypart.CanCollide = false
	steadypart.Size = Vector3.new(1,1,1)

	local function moveForwards(actionName, inputState, inputObject)
		if inputState == Enum.UserInputState.Begin then
			forwards = true
		end
		if inputState == Enum.UserInputState.End then
			forwards = false
		end
	end

	local function moveLeft(actionName, inputState, inputObject)
		if inputState == Enum.UserInputState.Begin then
			left = true
		end
		if inputState == Enum.UserInputState.End then
			left = false
		end
	end

	local function moveRight(actionName, inputState, inputObject)
		if inputState == Enum.UserInputState.Begin then
			right = true
		end
		if inputState == Enum.UserInputState.End then
			right = false
		end
	end

	local function moveBack(actionName, inputState, inputObject)
		if inputState == Enum.UserInputState.Begin then
			back = true
		end
		if inputState == Enum.UserInputState.End then
			back = false
		end
	end

	local d = true 
	hum.Touched:Connect(function(obj)
		if obj.Name == "BoostPart" and d then
			d = false
			local p = obj.Power
			multiplier *= p and p.Value or 5
			spawn(function()
				wait(2)
				d = true
			end)
		end
	end)

	local function removePitch(cf)
		local pitch, yaw, roll = cf:toEulerAnglesYXZ()
		return CFrame.fromEulerAnglesYXZ(0, yaw, roll) + cf.p
	end

	game.ReplicatedStorage.Events.Manipulate.Event:Connect(function(direction)
		multiplier += Vector3.new(direction.X, 0, direction.Y) * forwardsens
	end)
	
	rs.RenderStepped:Connect(function(dt)	

		local angle = cam.CFrame - cam.CFrame.Position
		local c = (angle + steadypart.CFrame.Position)
		steadypart.CFrame = removePitch(CFrame.fromMatrix(hum.Position, c.XVector, c.YVector, c.ZVector))

		if forwards then

			multiplier += steadypart.CFrame.LookVector * forwardsens
		end
		if back then
			multiplier += steadypart.CFrame.LookVector * backsens 
		end
		if left then
			multiplier += steadypart.CFrame.RightVector * leftsens
		end
		if right then
			multiplier += steadypart.CFrame.RightVector * rightsens
		end
		-- Movement is a BodyVelocity with a P of 100 and a MaxForce of (6000, 3000, 6000)
		multiplier *= 0.91

		hum.Movement.Velocity = multiplier * 2.23
		hum.FakeDrag.Force = -hum.Movement.Velocity
	end)

	cas:BindAction("moveForwardAction", moveForwards, false, Enum.PlayerActions.CharacterForward)
	cas:BindAction("moveBackwardAction", moveBack, false, Enum.PlayerActions.CharacterBackward)
	cas:BindAction("moveLeftAction", moveLeft, false, Enum.PlayerActions.CharacterLeft)
	cas:BindAction("moveRightAction", moveRight, false, Enum.PlayerActions.CharacterRight)
end

-- do initial check

if uis:GetLastInputType() == Enum.UserInputType.Touch then
	thumbframe:Enable(true)
end

uis.LastInputTypeChanged:Connect(function(InputType)
	if InputType == Enum.UserInputType.Keyboard or CheckForMouseInput(InputType) then -- If on computer.
		thumbframe:Enable(false)
	end

	if InputType == Enum.UserInputType.Touch then -- Might be on computer, but enabled touch controls.
		thumbframe:Enable(true)
	end

end)

uis.TouchTap:Connect(function()
	thumbframe:Enable(true)
end)

if plr.Character then
	CharacterAdded(plr.Character)
end

plr.CharacterAdded:Connect(CharacterAdded)

That works fine! The issue is when it comes to mobile controls. I’m using Roblox’s TouchThumbstick module (PlayerModule → ControlModule → TouchThumbstick) to create a thumbstick, which again works fine. The issue is that my code for detecting which way the marble should roll only works from certain camera angles. From certain camera angles, the ball rolls the opposite way the stick was moved. Here are the important parts of that script (it is very long so I couldn’t post the whole thing):

TouchThumbstick Code
local function Rotate(vector, angle)
	return Vector2.new(
		vector.X * math.cos(angle) + vector.Y * math.sin(angle),
		-vector.X * math.sin(angle) + vector.Y * math.cos(angle)
	)
end

local super = 0

-- when thumbstick is moved
		if inputObject == self.moveTouchObject then
			centerPosition = Vector2.new(self.thumbstickFrame.AbsolutePosition.x + self.thumbstickFrame.AbsoluteSize.x/2,
				self.thumbstickFrame.AbsolutePosition.y + self.thumbstickFrame.AbsoluteSize.y/2)
			
			super = Vector2.new(inputObject.Position.x - centerPosition.x, inputObject.Position.y - centerPosition.y)
			
			local look = workspace.CurrentCamera.CFrame.LookVector
			local angle = math.atan2(look.Z, look.X) + math.pi / 2
			
			local old = (inputObject.Position - inputObject.Delta)
			super = Rotate(super.Unit, angle)
			
		end
	
	-- when thumbstick movement has stopped
		if inputObject == self.moveTouchObject then
			self:OnInputEnded()
			super = 0
		end
	end)

game:GetService("RunService").Heartbeat:Connect(function(dt)
	if super ~= 0 then
		game.ReplicatedStorage.Events.Manipulate:Fire(super)
	end
end)


I think the reason why it only works with certain camera angles is because in my original script, I could change what the LookVector is being multiplied by ( multiplier += steadypart.CFrame.LookVector * 2.4 ). With this, it’s always being multiplied by a positive number despite the camera angle.

multiplier += Vector3.new(direction.X, 0, direction.Y) * forwardsens

What can I do to fix this? If you need any more context don’t hesitate to ask. Thanks!

P.S, I made a small image to help visualize my problem in simpler, less wordy form:

2 Likes

I think the only thing that really needs to be change is how I detect if the camera is in a bad angle. I tried using the angle variable but that proved to only work for one angle. Any help, please?

I recommend ditching handling input like that and using ControlModule:GetMoveVector() instead. ControlModule will figure out how to get the input from the user, whether it be thumbstick, joystick, WASD, arrow keys, tap-to-move, etc. Not only that, but it will also allow the user to change how they’re giving input in the middle of playing your game.

It will then convert all of this crazy input into one simple form for your code to handle.

I have a feeling this is all you need:

hum.Movement.Velocity = ControlModule:GetMoveVector() * 16 --16 is an example speed

The issue with that is that I can’t figure out how to make the MoveVector change relative to the camera. Holding A will always make you go left as if the camera is facing forward, not depending on where the camera is facing. Do you know a way I can make it camera relative?

Hmmm… maybe you could use the Camera CFrame’s LookVector? Or maybe try getting a custom direction vector from the camera’s position to the marble’s position.

Something like (marble.Position - camera.Position).Unit would give the directional vector from the camera to the marble. Then you could apply forces to the marble in relation to this directional vector.

I spent a few days implementing this and it ended up causing the same problem except for all platforms. I really want to keep my movement system as I love the way it feels. Do you think there is a solution with my current movement system?

I figured it already was camera-relative, but you can do this if it’s not:

local WorldMoveVector = CFrame.lookAt(Vector3.new(0, 0, 0), Camera.CFrame.LookVector * Vector3.new(1, 0, 1)) * ControlModule:GetMoveVector()

hum.Movement.Velocity = WorldMoveVector * 16 --16 is an example speed