Works just like the original, whilst also being more convenient to adjust during gameplay now that the rate is an attribute as shown in the new demo video
Also like its predecessor, it’s important that the script is left with the name “ControlScript” and is inside of StarterPlayerScripts so that it can work without the default control script interfering!
I also made an attribute for gamepad deadzone for games that want to be able to adjust it from a settings menu
It now uses type-checking too!
You can find it here (The original is still available in the marketplace if you prefer it)
Source code
--!strict
local ContextActionService = game:GetService("ContextActionService"):: ContextActionService
local GuiService = game:GetService("GuiService"):: GuiService
local Players = game:GetService("Players"):: Players
local RunService = game:GetService("RunService"):: RunService
local UserInputService = game:GetService("UserInputService"):: UserInputService
local player = Players.LocalPlayer:: Player
local playerGui = player:WaitForChild("PlayerGui"):: PlayerGui
local touchGui = script:WaitForChild("MC2_TouchGui"):: ScreenGui
local baseFrame = touchGui:WaitForChild("Base"):: Frame
local touchThumbstick = baseFrame:WaitForChild("Thumbstick"):: ImageLabel
local jumpButton = touchGui:WaitForChild("JumpButton"):: ImageButton
local inputTypeFilter = Instance.new("StringValue")
local humanoid
local rate -- In seconds. Large values (for example, using 2) result in a stronger and more noticeable effect
-- The rate is now an attribute of this script, which makes it easier to modify it during gameplay
local controllerDeadzone -- Is also an attribute so that you can give players the option to change it if you want to
local up1, down1, left1, right1 = 0, 0, 0, 0
local up2, down2, left2, right2 = 0, 0, 0, 0
local jump = false
local touchConnection: RBXScriptConnection?
local jumpButtonConnection: RBXScriptConnection?
local abs = math.abs
local clamp = math.clamp
local max = math.max
local min = math.min
local newVector3 = Vector3.new
local function onCharacterAdded(character: Model)
humanoid = character:WaitForChild("Humanoid"):: Humanoid
end
local function onRateChanged()
local newRate = script:GetAttribute("Rate"):: number
if newRate == 0 then
script:SetAttribute("Rate", 0.0001) -- Otherwise a divide by zero will happen
else
rate = newRate
end
end
local function onDeadzoneChanged()
controllerDeadzone = clamp(script:GetAttribute("Controller_Deadzone"), 0, 0.99)
end
local function mc2_Up(_, inputState: Enum.UserInputState)
up1 = if inputState == Enum.UserInputState.Begin then 1 else -1
end
local function mc2_Down(_, inputState: Enum.UserInputState)
down1 = if inputState == Enum.UserInputState.Begin then 1 else -1
end
local function mc2_Left(_, inputState: Enum.UserInputState)
left1 = if inputState == Enum.UserInputState.Begin then 1 else -1
end
local function mc2_Right(_, inputState: Enum.UserInputState)
right1 = if inputState == Enum.UserInputState.Begin then 1 else -1
end
local function deadzone(x: number): number
if abs(x) < controllerDeadzone then return 0 end
if x < 0 then return (x + controllerDeadzone) / (1 - controllerDeadzone) end
return (x - controllerDeadzone) / (1 - controllerDeadzone)
end
local function mc2_Thumbstick1(_, _, inputObject: InputObject)
up1 = deadzone(-inputObject.Position.Y)
left1 = deadzone(inputObject.Position.X)
end
local function mc2_Jump(_, inputState: Enum.UserInputState)
jump = (inputState == Enum.UserInputState.Begin)
end
local function mc2_Keyboard(deltaTime: number)
up2 = clamp(up2 + (deltaTime / rate * up1), 0, 1)
down2 = clamp(down2 + (deltaTime / rate * down1), 0, 1)
left2 = clamp(left2 + (deltaTime / rate * left1), 0, 1)
right2 = clamp(right2 + (deltaTime / rate * right1), 0, 0.99)
humanoid.Jump = jump
humanoid:Move(newVector3(right2 - left2, 0, down2 - up2), true)
end
local function mc2_Gamepad(deltaTime: number)
if up1 == 0 then
if up2 < 0 then
up2 = min(up2 + (deltaTime / rate), 0)
elseif up2 > 0 then
up2 = max(up2 - (deltaTime / rate), 0)
end
else
up2 = clamp(up2 + (deltaTime / rate * up1), -1, 1)
end
if left1 == 0 then
if left2 < 0 then
left2 = min(left2 + (deltaTime / rate), 0)
elseif left2 > 0 then
left2 = max(left2 - (deltaTime / rate), 0)
end
else
left2 = clamp(left2 + (deltaTime / rate * left1), -1, 1)
end
humanoid.Jump = jump
humanoid:Move(newVector3(left2, 0, up2), true)
end
local function unbindAllActions()
if touchConnection then
touchConnection:Disconnect()
touchConnection = nil
touchGui.Parent = script
end
if jumpButtonConnection then
jumpButtonConnection:Disconnect()
jumpButtonConnection = nil
end
ContextActionService:UnbindAction("MC2_Up")
ContextActionService:UnbindAction("MC2_Down")
ContextActionService:UnbindAction("MC2_Left")
ContextActionService:UnbindAction("MC2_Right")
ContextActionService:UnbindAction("MC2_Thumbstick1")
ContextActionService:UnbindAction("MC2_Jump")
RunService:UnbindFromRenderStep("MC2_Controls")
end
local function bindKeyboardActions()
ContextActionService:BindAction("MC2_Up", mc2_Up, false, Enum.KeyCode.W, Enum.KeyCode.Up)
ContextActionService:BindAction("MC2_Down", mc2_Down, false, Enum.KeyCode.S, Enum.KeyCode.Down)
ContextActionService:BindAction("MC2_Left", mc2_Left, false, Enum.KeyCode.A)
ContextActionService:BindAction("MC2_Right", mc2_Right, false, Enum.KeyCode.D)
ContextActionService:BindAction("MC2_Jump", mc2_Jump, false, Enum.KeyCode.Space)
RunService:BindToRenderStep("MC2_Controls", Enum.RenderPriority.Input.Value, mc2_Keyboard)
end
local function bindGamepadActions()
ContextActionService:BindAction("MC2_Thumbstick1", mc2_Thumbstick1, false, Enum.KeyCode.Thumbstick1)
ContextActionService:BindAction("MC2_Jump", mc2_Jump, false, Enum.KeyCode.ButtonA)
RunService:BindToRenderStep("MC2_Controls", Enum.RenderPriority.Input.Value, mc2_Gamepad)
end
local function bindTouchActions()
touchGui.Parent = playerGui
local switch = false
local fromScaleUDim2 = UDim2.fromScale
local function clampedilerp(x: number, min: number, max: number): number
if x < min then return 0 end
if x > max then return 1 end
return (x - min) / (max - min)
end
local function lerp(x: number, min: number, max: number): number
return (max - min) * x + min
end
jumpButtonConnection = jumpButton:GetPropertyChangedSignal("GuiState"):Connect(function()
if jumpButton.GuiState == Enum.GuiState.Press then
jump = true
else
jump = false
end
end)
touchConnection = touchThumbstick.InputBegan:Connect(function(input)
if switch then return end
if input.UserInputType == Enum.UserInputType.Touch then
switch = true
UserInputService.TouchEnded:Once(function()
RunService:UnbindFromRenderStep("MC2_Controls")
RunService:BindToRenderStep("MC2_Controls", Enum.RenderPriority.Input.Value, function(deltaTime: number)
if up2 < 0 then
up2 = min(up2 + (deltaTime / rate), 0)
elseif up2 > 0 then
up2 = max(up2 - (deltaTime / rate), 0)
end
if left2 < 0 then
left2 = min(left2 + (deltaTime / rate), 0)
elseif left2 > 0 then
left2 = max(left2 - (deltaTime / rate), 0)
end
humanoid.Jump = jump
humanoid:Move(newVector3(left2, 0, up2), true)
end)
touchThumbstick.Position = fromScaleUDim2(0.5, 0.5)
switch = false
end)
RunService:UnbindFromRenderStep("MC2_Controls")
RunService:BindToRenderStep("MC2_Controls", Enum.RenderPriority.Input.Value, function(deltaTime: number)
local mouseLocation = UserInputService:GetMouseLocation()
left1 = clampedilerp(
mouseLocation.X,
baseFrame.AbsolutePosition.X,
baseFrame.AbsolutePosition.X + baseFrame.AbsoluteSize.X)
up1 = clampedilerp(
mouseLocation.Y - GuiService:GetGuiInset().Y,
baseFrame.AbsolutePosition.Y,
baseFrame.AbsolutePosition.Y + baseFrame.AbsoluteSize.Y)
touchThumbstick.Position = fromScaleUDim2(left1, up1)
up1, left1 = lerp(up1, -1, 1), lerp(left1, -1, 1)
if up1 < up2 then
up2 = max(up2 - (deltaTime / rate), up1)
elseif up1 > up2 then
up2 = min(up2 + (deltaTime / rate), up1)
end
if left1 < left2 then
left2 = max(left2 - (deltaTime / rate), left1)
elseif left1 > left2 then
left2 = min(left2 + (deltaTime / rate), left1)
end
humanoid.Jump = jump
humanoid:Move(newVector3(left2, 0, up2), true)
end)
end
end)
end
local function onInputTypeFilterChanged(newInputType: string)
unbindAllActions()
if newInputType == "Keyboard" then
bindKeyboardActions()
elseif newInputType == "Gamepad" then
bindGamepadActions()
elseif newInputType == "Touch" then
bindTouchActions()
end
end
local function onLastInputTypeChanged(lastInputType: Enum.UserInputType)
if lastInputType == Enum.UserInputType.Keyboard or string.find(lastInputType.Name, "Mouse") then
inputTypeFilter.Value = "Keyboard"
elseif string.find(lastInputType.Name, "Gamepad") then
inputTypeFilter.Value = "Gamepad"
elseif lastInputType == Enum.UserInputType.Touch then
inputTypeFilter.Value = "Touch"
end
end
onCharacterAdded(player.Character or player.CharacterAdded:Wait())
onRateChanged()
onDeadzoneChanged()
if UserInputService.KeyboardEnabled then
bindKeyboardActions()
elseif UserInputService.GamepadEnabled then
bindGamepadActions()
elseif UserInputService.TouchEnabled then
bindTouchActions()
end
player.CharacterAdded:Connect(onCharacterAdded)
script:GetAttributeChangedSignal("Rate"):Connect(onRateChanged)
script:GetAttributeChangedSignal("Controller_Deadzone"):Connect(onDeadzoneChanged)
inputTypeFilter.Changed:Connect(onInputTypeFilterChanged)
UserInputService.LastInputTypeChanged:Connect(onLastInputTypeChanged)