Mobile WASD Joystick
Key RequiredUniversal

Mobile WASD Joystick

Universal Scripttheobscured102theobscured102 2.2K views 7K executesUpdated Apr 13, 2026
5.0/ 5(1 votes)
10100% Positive
Lua Script•129 lines (2967 chars)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local player = Players.LocalPlayer
local playerScripts = player:WaitForChild("PlayerScripts")
local controls = require(playerScripts:WaitForChild("PlayerModule")):GetControls()

local BIND_NAME = "CameraAlignedMobileMove"
local DEADZONE = 0.05
local SNAP_THRESHOLD = 0.3

local humanoid

local function getHumanoid()
	local character = player.Character or player.CharacterAdded:Wait()
	return character:WaitForChild("Humanoid")
end

local function unbind()
	RunService:UnbindFromRenderStep(BIND_NAME)
end

local function flatUnit(vector)
	local flat = Vector3.new(vector.X, 0, vector.Z)
	return flat.Magnitude > 0 and flat.Unit or Vector3.zero
end

local function snap(x, z)
	local sx = math.abs(x) > SNAP_THRESHOLD and (x > 0 and 1 or -1) or 0
	local sz = math.abs(z) > SNAP_THRESHOLD and (z > 0 and 1 or -1) or 0

	local vector = Vector3.new(sx, 0, sz)
	return vector.Magnitude > 0 and vector.Unit or Vector3.zero
end

local function hookCharacter(character)
	unbind()
	humanoid = character:WaitForChild("Humanoid")

	if not UserInputService.TouchEnabled then
		return
	end

	RunService:BindToRenderStep(BIND_NAME, Enum.RenderPriority.Input.Value + 1, function()
		if not humanoid or not humanoid.Parent then
			unbind()
			return
		end

		local camera = workspace.CurrentCamera
		if not camera then
			return
		end

		local move = controls:GetMoveVector()
		if move.Magnitude <= DEADZONE then
			return
		end

		local forward = flatUnit(camera.CFrame.LookVector)
		local right = flatUnit(camera.CFrame.RightVector)

		if forward == Vector3.zero or right == Vector3.zero then
			return
		end

		local snapped = snap(move.X, move.Z)
		local finalMove

		if snapped ~= Vector3.zero then
			finalMove = (right * snapped.X) - (forward * snapped.Z)
		else
			finalMove = (right * move.X) - (forward * move.Z)
		end

		if finalMove.Magnitude > 0 then
			humanoid:Move(finalMove.Unit, false)
		end
	end)
end

player.CharacterAdded:Connect(hookCharacter)

if player.Character then
	hookCharacter(player.Character)
else
	humanoid = getHumanoid()
end

RunService.RenderStepped:Connect(function()
	if UserInputService.TouchEnabled then
		return
	end

	if not humanoid or not humanoid.Parent then
		return
	end

	local camera = workspace.CurrentCamera
	if not camera then
		return
	end

	local move = humanoid.MoveDirection
	if move.Magnitude < 0.1 then
		return
	end

	local forward = flatUnit(camera.CFrame.LookVector)
	local right = flatUnit(camera.CFrame.RightVector)

	if forward == Vector3.zero or right == Vector3.zero then
		return
	end

	local x = move:Dot(right)
	local z = move:Dot(forward)
	local snapped = snap(x, z)

	if snapped == Vector3.zero then
		return
	end

	local finalMove = (right * snapped.X) + (forward * snapped.Z)
	if finalMove.Magnitude > 0 then
		humanoid:Move(finalMove.Unit, false)
	end
end)

Features & Information

Just a silly script I put together. It makes your joystick behave more like WASD on a keyboard. It also supports proper diagonal movement, so inputs combine the same way WASD does.

Community Comments (1)

BotzBotzApr 13, 2026

I've been looking for this script everywhere thank you man

More Scripts for Universal Scripts