-- VIRTUAL KEYBOARD PRO -- Roblox Studio LocalScript -- StarterPlayer > StarterPlayerScripts local Players = game:GetService("Players") local UIS = game:GetService("UserInputService") local RunService = game:GetService("RunService") local player = Players.LocalPlayer local playerGui = player:WaitForChild("PlayerGui") local old = playerGui:FindFirstChild("VirtualKeyboard") if old then old:Destroy() end local gui = Instance.new("ScreenGui") gui.Name = "VirtualKeyboard" gui.ResetOnSpawn = false gui.DisplayOrder = 100 gui.Parent = playerGui -- Default positions local defaults = { W = UDim2.new(0, 100, 0, 245), A = UDim2.new(0, 45, 0, 300), S = UDim2.new(0, 100, 0, 300), D = UDim2.new(0, 155, 0, 300), SHIFT = UDim2.new(1, -125, 1, -115), } local held = {W=false, A=false, S=false, D=false} local activeInputs = {} local buttons = {} local locked = false local shiftOn = false local drag = nil local sliderDrag = nil local savedAutoRotate = true local savedCameraOffset = Vector3.zero -- Size settings local minSize = 30 local maxSize = 100 local currentSize = 50 local function createButton(name, text, position, size, color) local b = Instance.new("TextButton") b.Name = name b.Text = text b.Position = position b.Size = size b.BackgroundColor3 = color or Color3.fromRGB(42, 47, 62) b.TextColor3 = Color3.new(1, 1, 1) b.Font = Enum.Font.GothamBold b.TextScaled = true b.BorderSizePixel = 0 b.Active = true b.AutoButtonColor = true b.Parent = gui local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 8) corner.Parent = b return b end local function setHeld(key, value) held[key] = value local b = buttons[key] if b then b.BackgroundColor3 = value and Color3.fromRGB(35, 145, 95) or Color3.fromRGB(42, 47, 62) end end -- WASD buttons for _, key in ipairs({"W", "A", "S", "D"}) do local b = createButton( key, key, defaults[key], UDim2.fromOffset(currentSize, currentSize) ) buttons[key] = b b.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then activeInputs[key] = input setHeld(key, true) end end) end -- Shift button local shiftButton = createButton( "SHIFT", "SHIFT", defaults.SHIFT, UDim2.fromOffset(currentSize * 2, currentSize * 0.9) ) buttons.SHIFT = shiftButton -- Lock and reset local lockButton = createButton( "LOCK", "LOCK ALL", UDim2.new(0, 15, 0, 35), UDim2.fromOffset(110, 35), Color3.fromRGB(30, 130, 85) ) local resetButton = createButton( "RESET", "RESET", UDim2.new(0, 135, 0, 35), UDim2.fromOffset(85, 35) ) -- Size toggle local sizeToggle = createButton( "SizeToggle", "SIZE +", UDim2.new(0, 15, 0, 78), UDim2.fromOffset(100, 32), Color3.fromRGB(55, 100, 180) ) -- Size slider panel local sizePanel = Instance.new("Frame") sizePanel.Name = "SizePanel" sizePanel.Size = UDim2.fromOffset(245, 72) sizePanel.Position = UDim2.new(0, 15, 0, 115) sizePanel.BackgroundColor3 = Color3.fromRGB(28, 32, 44) sizePanel.BorderSizePixel = 0 sizePanel.Visible = false sizePanel.ZIndex = 5 sizePanel.Parent = gui local panelCorner = Instance.new("UICorner") panelCorner.CornerRadius = UDim.new(0, 9) panelCorner.Parent = sizePanel local slider = Instance.new("Frame") slider.Name = "Slider" slider.Size = UDim2.new(1, -30, 0, 6) slider.Position = UDim2.new(0, 15, 0, 28) slider.BackgroundColor3 = Color3.fromRGB(95, 105, 125) slider.BorderSizePixel = 0 slider.Active = true slider.ZIndex = 6 slider.Parent = sizePanel local sliderCorner = Instance.new("UICorner") sliderCorner.CornerRadius = UDim.new(1, 0) sliderCorner.Parent = slider local knob = Instance.new("TextButton") knob.Name = "Knob" knob.Text = "" knob.Size = UDim2.fromOffset(18, 18) knob.AnchorPoint = Vector2.new(0.5, 0.5) knob.Position = UDim2.new( (currentSize - minSize) / (maxSize - minSize), 0, 0.5, 0 ) knob.BackgroundColor3 = Color3.fromRGB(235, 240, 255) knob.BorderSizePixel = 0 knob.Active = true knob.ZIndex = 7 knob.Parent = slider local knobCorner = Instance.new("UICorner") knobCorner.CornerRadius = UDim.new(1, 0) knobCorner.Parent = knob local sizeLabel = Instance.new("TextLabel") sizeLabel.Size = UDim2.new(1, 0, 0, 22) sizeLabel.Position = UDim2.new(0, 0, 0, 43) sizeLabel.BackgroundTransparency = 1 sizeLabel.TextColor3 = Color3.new(1, 1, 1) sizeLabel.TextSize = 12 sizeLabel.Font = Enum.Font.Gotham sizeLabel.ZIndex = 6 sizeLabel.Parent = sizePanel -- Apply a size to all five controls local function applySize(value) currentSize = math.clamp(value, minSize, maxSize) local alpha = (currentSize - minSize) / (maxSize - minSize) knob.Position = UDim2.new(alpha, 0, 0.5, 0) sizeLabel.Text = "SIZE: " .. math.floor(currentSize) for name, b in pairs(buttons) do if name == "SHIFT" then b.Size = UDim2.fromOffset( currentSize * 2, currentSize * 0.9 ) else b.Size = UDim2.fromOffset(currentSize, currentSize) end end end local function updateSlider(inputX) local left = slider.AbsolutePosition.X local width = slider.AbsoluteSize.X if width <= 0 then return end local alpha = math.clamp((inputX - left) / width, 0, 1) applySize(minSize + alpha * (maxSize - minSize)) end local function beginSlider(input) sliderDrag = input updateSlider(input.Position.X) end knob.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then beginSlider(input) end end) slider.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then beginSlider(input) end end) -- Independently drag every movement button and SHIFT. local function enableDrag(button) button.InputBegan:Connect(function(input) if locked then return end if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then drag = { button = button, input = input, start = input.Position, position = button.Position, } end end) end for _, b in pairs(buttons) do enableDrag(b) end UIS.InputChanged:Connect(function(input) if sliderDrag and ( input == sliderDrag or input.UserInputType == Enum.UserInputType.MouseMovement ) then updateSlider(input.Position.X) end if drag and not locked and ( input == drag.input or input.UserInputType == Enum.UserInputType.MouseMovement ) then local delta = input.Position - drag.start drag.button.Position = UDim2.new( drag.position.X.Scale, drag.position.X.Offset + delta.X, drag.position.Y.Scale, drag.position.Y.Offset + delta.Y ) end end) UIS.InputEnded:Connect(function(input) if sliderDrag and ( input == sliderDrag or input.UserInputType == Enum.UserInputType.MouseButton1 ) then sliderDrag = nil end if drag and ( input == drag.input or input.UserInputType == Enum.UserInputType.MouseButton1 ) then drag = nil end -- Release only the matching WASD touch/click. for key, activeInput in pairs(activeInputs) do if activeInput == input then activeInputs[key] = nil setHeld(key, false) end end end) -- Lock/unlock all positions lockButton.Activated:Connect(function() locked = not locked lockButton.Text = locked and "UNLOCK ALL" or "LOCK ALL" lockButton.BackgroundColor3 = locked and Color3.fromRGB(175, 55, 55) or Color3.fromRGB(30, 130, 85) if locked then drag = nil end end) -- Reset positions resetButton.Activated:Connect(function() for name, position in pairs(defaults) do if buttons[name] then buttons[name].Position = position end end end) -- Show/hide size slider sizeToggle.Activated:Connect(function() sizePanel.Visible = not sizePanel.Visible sizeToggle.Text = sizePanel.Visible and "SIZE -" or "SIZE +" end) -- Shift Lock-style camera and character facing local function setShiftMode(enabled) shiftOn = enabled local character = player.Character local humanoid = character and character:FindFirstChildOfClass("Humanoid") if humanoid then if enabled then savedAutoRotate = humanoid.AutoRotate savedCameraOffset = humanoid.CameraOffset humanoid.AutoRotate = false humanoid.CameraOffset = Vector3.new(1.5, 0, 0) else humanoid.AutoRotate = savedAutoRotate humanoid.CameraOffset = savedCameraOffset end end shiftButton.BackgroundColor3 = enabled and Color3.fromRGB(35, 145, 95) or Color3.fromRGB(42, 47, 62) end shiftButton.Activated:Connect(function() setShiftMode(not shiftOn) end) RunService:BindToRenderStep( "VirtualKeyboardShiftLock", Enum.RenderPriority.Character.Value + 1, function() if not shiftOn then return end local character = player.Character local humanoid = character and character:FindFirstChildOfClass("Humanoid") local root = character and character:FindFirstChild("HumanoidRootPart") local camera = workspace.CurrentCamera if not humanoid or humanoid.Health <= 0 or not root or not camera then return end local look = camera.CFrame.LookVector local flat = Vector3.new(look.X, 0, look.Z) if flat.Magnitude > 0.001 then root.CFrame = CFrame.lookAt( root.Position, root.Position + flat.Unit ) end end ) -- Camera-relative movement, including while jumping RunService:BindToRenderStep( "VirtualKeyboardMovement", Enum.RenderPriority.Input.Value + 1, function() local character = player.Character local humanoid = character and character:FindFirstChildOfClass("Humanoid") local camera = workspace.CurrentCamera if not humanoid or humanoid.Health <= 0 or not camera then return end local look = camera.CFrame.LookVector local right = camera.CFrame.RightVector local forward = Vector3.new(look.X, 0, look.Z) local side = Vector3.new(right.X, 0, right.Z) if forward.Magnitude > 0 then forward = forward.Unit end if side.Magnitude > 0 then side = side.Unit end local direction = Vector3.zero if held.W then direction += forward end if held.S then direction -= forward end if held.D then direction += side end if held.A then direction -= side end humanoid:Move(direction, false) end ) -- Initialize slider size applySize(currentSize) -- Reset shift state after respawn player.CharacterAdded:Connect(function(character) for key in pairs(held) do activeInputs[key] = nil setHeld(key, false) end shiftOn = false task.wait(0.3) local humanoid = character:FindFirstChildOfClass("Humanoid") if humanoid then humanoid.CameraOffset = Vector3.zero humanoid.AutoRotate = true end shiftButton.BackgroundColor3 = Color3.fromRGB(42, 47, 62) end)