Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- MobileJoystick LocalScript v3
- -- StarterPlayerScripts
- local Players = game:GetService("Players")
- local UserInputService = game:GetService("UserInputService")
- local RunService = game:GetService("RunService")
- local GuiService = game:GetService("GuiService")
- local player = Players.LocalPlayer
- local character = player.Character or player.CharacterAdded:Wait()
- local humanoid = character:WaitForChild("Humanoid")
- local rootPart = character:WaitForChild("HumanoidRootPart")
- local camera = workspace.CurrentCamera
- -- ── Velocidad ──
- local FINAL_SPEED = math.floor(16 * 1.06)
- local TP_SPEED = math.floor(FINAL_SPEED * 1.30) -- +10% siempre activo
- humanoid.WalkSpeed = TP_SPEED
- if not UserInputService.TouchEnabled then return end
- -- ── Deshabilitar joystick nativo ──
- local ok, PlayerModule = pcall(require, player.PlayerScripts:WaitForChild("PlayerModule"))
- if ok and PlayerModule then
- PlayerModule:GetControls():Disable()
- end
- -- ════════════════════════════════
- -- GUI
- -- ════════════════════════════════
- local gui = Instance.new("ScreenGui")
- gui.Name = "FFJoystick"
- gui.ResetOnSpawn = false
- gui.IgnoreGuiInset = true
- gui.Parent = player.PlayerGui
- -- Zona táctil izquierda (tamaño original)
- local zone = Instance.new("TextButton")
- zone.Size = UDim2.new(0.5, 0, 0.6, 0)
- zone.Position = UDim2.new(0, 0, 0.4, 0)
- zone.BackgroundTransparency = 1
- zone.Text = ""
- zone.Parent = gui
- -- Base (oculta)
- local base = Instance.new("Frame")
- base.Size = UDim2.new(0, 140, 0, 140)
- base.AnchorPoint = Vector2.new(0.5, 0.5)
- base.BackgroundColor3 = Color3.fromRGB(10, 10, 10)
- base.BackgroundTransparency = 0.45
- base.BorderSizePixel = 0
- base.Visible = false
- base.Parent = gui
- Instance.new("UICorner", base).CornerRadius = UDim.new(1, 0)
- local s1 = Instance.new("UIStroke", base)
- s1.Color = Color3.fromRGB(180, 180, 180); s1.Thickness = 1.5; s1.Transparency = 0.5
- -- Letras WASD
- for _, d in ipairs({
- {t="W", x=0.5, y=0.10},
- {t="A", x=0.10,y=0.5 },
- {t="S", x=0.5, y=0.87},
- {t="D", x=0.87,y=0.5 },
- }) do
- local l = Instance.new("TextLabel")
- l.Size = UDim2.new(0,20,0,20)
- l.AnchorPoint = Vector2.new(0.5,0.5)
- l.Position = UDim2.new(d.x,0,d.y,0)
- l.BackgroundTransparency = 1
- l.Text = d.t
- l.TextColor3 = Color3.fromRGB(200, 200, 200)
- l.TextScaled = true
- l.Font = Enum.Font.GothamBold
- l.Parent = base
- end
- -- Thumb
- local thumb = Instance.new("Frame")
- thumb.Size = UDim2.new(0,52,0,52)
- thumb.AnchorPoint = Vector2.new(0.5,0.5)
- thumb.Position = UDim2.new(0.5,0,0.5,0)
- thumb.BackgroundColor3 = Color3.fromRGB(35, 35, 35)
- thumb.BackgroundTransparency = 0.3
- thumb.BorderSizePixel = 0
- thumb.Parent = base
- Instance.new("UICorner", thumb).CornerRadius = UDim.new(1, 0)
- local s2 = Instance.new("UIStroke", thumb)
- s2.Color = Color3.fromRGB(220,220,220); s2.Thickness = 1.5; s2.Transparency = 0.5
- -- Botón de salto
- local jumpBtn = Instance.new("TextButton")
- jumpBtn.Size = UDim2.new(0,80,0,80)
- jumpBtn.AnchorPoint = Vector2.new(1,1)
- jumpBtn.Position = UDim2.new(1,-20,1,-30)
- jumpBtn.BackgroundColor3 = Color3.fromRGB(230,230,230)
- jumpBtn.BackgroundTransparency = 0.1
- jumpBtn.BorderSizePixel = 0
- jumpBtn.Text = "JUMP"
- jumpBtn.TextColor3 = Color3.fromRGB(40,40,40)
- jumpBtn.TextScaled = true
- jumpBtn.Font = Enum.Font.GothamBold
- jumpBtn.Parent = gui
- Instance.new("UICorner", jumpBtn).CornerRadius = UDim.new(1,0)
- jumpBtn.MouseButton1Down:Connect(function()
- if humanoid then humanoid.Jump = true end
- end)
- -- ════════════════════════════════
- -- ESTADO DEL JOYSTICK
- -- ════════════════════════════════
- local active = false
- local activeInput = nil
- local MAX_R = 55 -- radio máximo del thumb en px
- local DEAD = 14 -- dead zone en px
- -- W A S D como booleanos (se activan por zona del joystick)
- local keys = {w=false, a=false, s=false, d=false}
- local baseCenter = Vector2.new(0, 0) -- se fija al tocar
- local function updateKeys(touchPos)
- local delta = touchPos - baseCenter
- local dist = delta.Magnitude
- -- Mover thumb visualmente
- local clamped = math.min(dist, MAX_R)
- local dir = dist > 1 and delta.Unit or Vector2.new(0,0)
- thumb.Position = UDim2.new(0.5, dir.X * clamped, 0.5, dir.Y * clamped)
- -- Calcular qué teclas están "presionadas"
- -- Dividir en zonas: arriba(W), abajo(S), izquierda(A), derecha(D)
- -- Con umbral angular de 67.5° por tecla (permite diagonales)
- if dist < DEAD then
- keys.w=false; keys.a=false; keys.s=false; keys.d=false
- return
- end
- local angle = math.deg(math.atan2(delta.Y, delta.X))
- -- atan2: derecha=0, abajo=90, izquierda=180, arriba=-90
- keys.w = false; keys.a = false; keys.s = false; keys.d = false
- -- W+A y W+D se combinan (diagonales adelante)
- -- S, A solo, D solo son puros
- if angle > -22.5 and angle <= 22.5 then keys.d = true -- D puro
- elseif angle > 22.5 and angle <= 157.5 then keys.s = true -- S puro
- elseif angle > 157.5 or angle <= -157.5 then keys.a = true -- A puro
- elseif angle > -157.5 and angle <= -112.5 then keys.w=true; keys.a=true -- W+A diagonal
- elseif angle > -112.5 and angle <= -67.5 then keys.w = true -- W puro
- elseif angle > -67.5 and angle <= -22.5 then keys.w=true; keys.d=true -- W+D diagonal
- end
- end
- local function resetKeys()
- keys.w=false; keys.a=false; keys.s=false; keys.d=false
- thumb.Position = UDim2.new(0.5,0,0.5,0)
- base.Visible = false
- active = false
- activeInput = nil
- end
- zone.InputBegan:Connect(function(input)
- if input.UserInputType == Enum.UserInputType.Touch and not active then
- active = true
- activeInput = input
- -- input.Position está en coords de pantalla SIN el inset del GUI.
- -- Como IgnoreGuiInset=true el GUI sí usa coords absolutas,
- -- hay que sumarle el inset para que el Frame aparezca exactamente
- -- donde tocó el dedo (fix: joystick ya no aparece más arriba).
- local px = input.Position.X
- local py = input.Position.Y
- local inset = GuiService:GetGuiInset()
- baseCenter = Vector2.new(px, py) -- raw, para calcular delta del thumb
- base.Position = UDim2.fromOffset(px, py + inset.Y) -- coords absolutas GUI
- base.Visible = true
- updateKeys(Vector2.new(px, py))
- end
- end)
- UserInputService.TouchMoved:Connect(function(input)
- if active and input == activeInput then
- updateKeys(Vector2.new(input.Position.X, input.Position.Y))
- end
- end)
- UserInputService.TouchEnded:Connect(function(input)
- if active and input == activeInput then
- resetKeys()
- end
- end)
- -- ════════════════════════════════
- -- MOVIMIENTO (igual que PC)
- -- ════════════════════════════════
- -- En PC, Roblox suma los vectores de cada tecla presionada:
- -- W → camCFrame.LookVector (plano XZ)
- -- S → -camCFrame.LookVector
- -- A → -camCFrame.RightVector
- -- D → camCFrame.RightVector
- -- Luego normaliza si magnitude > 0
- -- Eso es exactamente lo que hacemos aquí.
- local lockedDir = nil
- local wasInAir = false
- RunService.Heartbeat:Connect(function(dt)
- if not character or not character.Parent then return end
- local cf = camera.CFrame
- local fwd = Vector3.new(cf.LookVector.X, 0, cf.LookVector.Z).Unit
- local right = Vector3.new(cf.RightVector.X, 0, cf.RightVector.Z).Unit
- local moveDir = Vector3.new(0,0,0)
- if keys.w then moveDir = moveDir + fwd end
- if keys.s then moveDir = moveDir - fwd end
- if keys.d then moveDir = moveDir + right end
- if keys.a then moveDir = moveDir - right end
- if moveDir.Magnitude > 0 then
- moveDir = moveDir.Unit
- end
- local isInAir = humanoid.FloorMaterial == Enum.Material.Air
- -- Rotar personaje instantáneamente hacia la dirección (solo en el suelo)
- if moveDir.Magnitude > 0 and not isInAir then
- rootPart.CFrame = CFrame.new(rootPart.Position, rootPart.Position + moveDir)
- end
- if isInAir then
- if not wasInAir then
- -- Bloquear dirección al saltar
- lockedDir = moveDir.Magnitude > 0 and moveDir
- or (rootPart.CFrame.LookVector * Vector3.new(1,0,1)).Unit
- end
- if lockedDir then
- humanoid:Move(lockedDir, false)
- end
- else
- lockedDir = nil
- humanoid:Move(moveDir, false)
- end
- wasInAir = isInAir
- end)
- -- ════════════════════════════════
- -- RESPAWN
- -- ════════════════════════════════
- player.CharacterAdded:Connect(function(newChar)
- character = newChar
- humanoid = newChar:WaitForChild("Humanoid")
- rootPart = newChar:WaitForChild("HumanoidRootPart")
- -- Roblox reactiva el joystick nativo en cada respawn, hay que deshabilitarlo otra vez
- local okR, PM = pcall(require, player.PlayerScripts:WaitForChild("PlayerModule"))
- if okR and PM then
- PM:GetControls():Disable()
- end
- -- Esperar un frame: el Humanoid puede resetear WalkSpeed justo después de aparecer
- RunService.Heartbeat:Wait()
- humanoid.WalkSpeed = TP_SPEED
- resetKeys()
- wasInAir = false
- lockedDir = nil
- end)
Advertisement
Add Comment
Please, Sign In to add comment