I want to create a custom make mobile control that look like original built-in mobile control, as the same time is more customizable.
Here is the video demo
Here is the code
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local joystickBG = script.Parent
local knob = joystickBG:WaitForChild("Knob")
local dragging = false
local dragTouchId = nil
local radius = joystickBG.AbsoluteSize.X / 2
local moveDirection = Vector2.new(0, 0)
local currentMoveDirection = Vector2.new(0, 0)
local DEADZONE = 0.12 -- 12% deadzone, tweak as needed
local RELEASE_DECAY = 0.18 -- seconds to fully stop after release
-- Utility: Get direction and distance from center
local function getDirectionVector(pos)
local center = joystickBG.AbsolutePosition + joystickBG.AbsoluteSize / 2
local offset = Vector2.new(pos.X, pos.Y) - Vector2.new(center.X, center.Y)
local distance = math.min(offset.Magnitude, radius)
local direction = offset.Magnitude > 0 and offset.Unit or Vector2.new(0, 0)
return direction, distance
end
local function updateKnob(pos)
local direction, distance = getDirectionVector(pos)
-- Clamp knob to circle
knob.Position = UDim2.new(0.5, direction.X * distance, 0.5, direction.Y * distance)
-- Deadzone logic
if distance / radius < DEADZONE then
moveDirection = Vector2.new(0, 0)
else
-- Scale so full radius = 1, deadzone = 0
local scaled = (distance - radius * DEADZONE) / (radius * (1 - DEADZONE))
moveDirection = direction * math.clamp(scaled, 0, 1)
end
end
local function resetKnob()
-- Smoothly reset the knob to center
TweenService:Create(knob, TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {
Position = UDim2.new(0.5, 0, 0.5, 0)
}):Play()
moveDirection = Vector2.new(0, 0)
end
-- Touch handling: robust, works even if finger leaves joystick frame
UIS.TouchStarted:Connect(function(input, processed)
if processed then return end
if not dragging then
-- Only start drag if touch is inside joystick
local absPos = Vector2.new(input.Position.X, input.Position.Y)
local center = joystickBG.AbsolutePosition + joystickBG.AbsoluteSize / 2
if (absPos - Vector2.new(center.X, center.Y)).Magnitude <= radius then
dragging = true
dragTouchId = input.UserInputState == Enum.UserInputState.Begin and input.TouchId or nil
updateKnob(input.Position)
end
end
end)
UIS.TouchMoved:Connect(function(input, processed)
if dragging and (not dragTouchId or input.TouchId == dragTouchId) then
updateKnob(input.Position)
end
end)
UIS.TouchEnded:Connect(function(input, processed)
if dragging and (not dragTouchId or input.TouchId == dragTouchId) then
dragging = false
dragTouchId = nil
resetKnob()
end
end)
-- Fallback for mouse input (for testing in studio)
joystickBG.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
dragging = true
updateKnob(input.Position)
end
end)
joystickBG.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
dragging = false
resetKnob()
end
end)
UIS.InputChanged:Connect(function(input)
if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
updateKnob(input.Position)
end
end)
-- Move the player using the joystick direction, matching built-in Roblox joystick
RunService.RenderStepped:Connect(function(dt)
local player = Players.LocalPlayer
local character = player.Character
if character then
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
-- Smoothly interpolate movement direction when released
if dragging and (moveDirection.Magnitude > 0) then
currentMoveDirection = moveDirection
else
-- Lerp to zero over RELEASE_DECAY seconds
currentMoveDirection = currentMoveDirection:Lerp(Vector2.new(0,0), math.clamp(dt / RELEASE_DECAY, 0, 1))
end
local camera = workspace.CurrentCamera
if camera then
-- Get camera's CFrame
local camCF = camera.CFrame
-- Project camera's LookVector and RightVector onto XZ plane and normalize
local camForward = Vector3.new(camCF.LookVector.X, 0, camCF.LookVector.Z)
if camForward.Magnitude > 0 then
camForward = camForward.Unit
end
local camRight = Vector3.new(camCF.RightVector.X, 0, camCF.RightVector.Z)
if camRight.Magnitude > 0 then
camRight = camRight.Unit
end
-- Y axis on joystick is -1 at top, +1 at bottom, so invert Y for correct forward
local moveVec = camRight * currentMoveDirection.X + camForward * -currentMoveDirection.Y
if moveVec.Magnitude > 1 then
moveVec = moveVec.Unit
end
humanoid:Move(moveVec, false)
end
end
end
end)
MobileControls.rbxm (8.7 KB)