Files
2025-09-18 17:55:52 -04:00

1681 lines
60 KiB
Lua

-- This is responsible for all touch controls we show (as of this writing, only on iOS/Android)
-- this includes character move thumbsticks, and buttons for jump, use of items, camera, etc.
-- Written by Ben Tkacheff/Jason Roth, Copyright Roblox 2013
-- fast flag for new controls
local touchFlagExists, touchFlagValue = pcall(function () return settings():GetFFlag("NewTouchControlScript") end)
local hasNewTouchControls = touchFlagExists and touchFlagValue
if hasNewTouchControls then -- NEW TOUCH CONTROLS WITH THUMBPAD
local UserInputService = game:GetService('UserInputService')
local GuiService = game:GetService('GuiService')
local Players = game:GetService('Players')
local success = pcall(function() UserInputService:IsLuaTouchControls() end)
if not success then
script:Destroy()
end
--[[ Variables ]]--
local CoreGui = game:WaitForChild('CoreGui')
local RobloxGui = CoreGui:WaitForChild('RobloxGui')
local ScreenResolution = GuiService:GetScreenResolution()
local function isSmallScreenDevice()
return ScreenResolution.y <= 500
end
local GameSettings = UserSettings().GameSettings
local Player = nil
while not Players.LocalPlayer do wait() end
Player = Players.LocalPlayer
--[[ Constants ]]--
local CAMERA_ZOOM_SENSITIVITY = 0.03
local CAMERA_ROTATE_SENSITIVITY = 0.007
local JUMP_BUTTON_SIZE = isSmallScreenDevice() and 70 or 90
local THUMBSTICK_SIZE = isSmallScreenDevice() and 70 or 120
--[[ Images ]]--
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/TouchControlsSheet.png"
local DPAD_SHEET = "rbxasset://textures/ui/DPadSheet.png"
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Helper Functions
--
-- used to create arrows for both dpad and new controls
local function createArrowLabel(name, parent, pos, size, spOffset, spSize)
local arrow = Instance.new('ImageLabel')
arrow.Name = name
arrow.Image = DPAD_SHEET
arrow.ImageRectOffset = spOffset
arrow.ImageRectSize = spSize
arrow.BackgroundTransparency = 1
arrow.Size = size
arrow.Position = pos
arrow.Parent = parent
return arrow
end
local function createControlFrame(name, position, size)
local frame = Instance.new('Frame')
frame.Name = name
frame.Active = true
frame.Size = UDim2.new(0, size, 0, size)
frame.Position = position
frame.BackgroundTransparency = 1
return frame
end
local function createOuterImage(name, position, parent)
local image = Instance.new('ImageLabel')
image.Name = name
image.Image = TOUCH_CONTROL_SHEET
image.ImageRectOffset = Vector2.new(0, 0)
image.ImageRectSize = Vector2.new(220, 220)
image.BackgroundTransparency = 1
image.Size = UDim2.new(0, THUMBSTICK_SIZE, 0, THUMBSTICK_SIZE)
image.Position = position
image.Parent = parent
return image
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Jump Controls
--
local JumpButton = nil
local JumpTouchObject = nil
local function setupJumpButton(parentFrame)
if not JumpButton then
JumpButton = Instance.new('ImageButton')
JumpButton.Name = "JumpButton"
JumpButton.BackgroundTransparency = 1
JumpButton.Image = TOUCH_CONTROL_SHEET
JumpButton.ImageRectOffset = Vector2.new(176, 222)
JumpButton.ImageRectSize = Vector2.new(174, 174)
JumpButton.Size = UDim2.new(0, JUMP_BUTTON_SIZE, 0, JUMP_BUTTON_SIZE)
if isSmallScreenDevice() then
JumpButton.Position = UDim2.new(1, JUMP_BUTTON_SIZE * -2.25, 1, -JUMP_BUTTON_SIZE - 20)
else
JumpButton.Position = UDim2.new(1, JUMP_BUTTON_SIZE * -2.75, 1, -JUMP_BUTTON_SIZE - 120)
end
local playerJump_func = Player.JumpCharacter
local doJumpLoop = function()
while JumpTouchObject do
if Player then
playerJump_func(Player)
end
wait(1/60)
end
end
JumpButton.InputBegan:connect(function(inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch or JumpTouchObject then
return
end
JumpTouchObject = inputObject
JumpButton.ImageRectOffset = Vector2.new(0, 222)
doJumpLoop()
end)
JumpButton.InputEnded:connect(function(inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
if inputObject == JumpTouchObject then
JumpTouchObject = nil
JumpButton.ImageRectOffset = Vector2.new(176, 222)
end
end)
JumpButton.Parent = parentFrame
JumpButton.Visible = not UserInputService.ModalEnabled
end
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Thumbstick Controls
--
local ThumbstickFrame = nil
local IsFollowStick = true
local function setupThumbstick(parentFrame)
if ThumbstickFrame then return end
local isSmallScreen = isSmallScreenDevice()
local position = isSmallScreen and UDim2.new(0, (THUMBSTICK_SIZE/2) - 10, 1, -THUMBSTICK_SIZE - 20) or
UDim2.new(0, THUMBSTICK_SIZE/2, 1, -THUMBSTICK_SIZE * 1.75)
ThumbstickFrame = createControlFrame("ThumbstickFrame", position, THUMBSTICK_SIZE)
local outerImage = createOuterImage("OuterImage", UDim2.new(0, 0, 0, 0), ThumbstickFrame)
local innerImage = Instance.new('ImageLabel')
innerImage.Name = "InnerImage"
innerImage.Image = TOUCH_CONTROL_SHEET
innerImage.ImageRectOffset = Vector2.new(220, 0)
innerImage.ImageRectSize = Vector2.new(111, 111)
innerImage.BackgroundTransparency = 1
innerImage.Size = UDim2.new(0, THUMBSTICK_SIZE/2, 0, THUMBSTICK_SIZE/2)
innerImage.Position = UDim2.new(0, ThumbstickFrame.Size.X.Offset/2 - THUMBSTICK_SIZE/4, 0, ThumbstickFrame.Size.Y.Offset/2 - THUMBSTICK_SIZE/4)
innerImage.ZIndex = 2
innerImage.Parent = ThumbstickFrame
local centerPosition = nil
local deadZone = 0.05
local function doMove(direction)
local inputAxis = direction / (THUMBSTICK_SIZE/2)
-- Scaled Radial Dead Zone
local inputAxisMagnitude = inputAxis.magnitude
if inputAxisMagnitude < deadZone then
inputAxis = Vector3.new()
else
inputAxis = inputAxis.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
-- NOTE: Making this a unit vector will cause the player to instantly go max speed
inputAxis = Vector3.new(inputAxis.x, 0, inputAxis.y)
end
if Player then
Player:Move(inputAxis, true)
end
end
local function moveStick(position)
local relativePosition = Vector2.new(position.x - centerPosition.x, position.y - centerPosition.y)
local length = relativePosition.magnitude
local maxLength = ThumbstickFrame.AbsoluteSize.x/2
if IsFollowStick and length > maxLength then
local offset = relativePosition.unit * maxLength
ThumbstickFrame.Position = UDim2.new(
0, position.x - ThumbstickFrame.AbsoluteSize.x/2 - offset.x,
0, position.y - ThumbstickFrame.AbsoluteSize.y/2 - offset.y)
else
length = math.min(length, maxLength)
relativePosition = relativePosition.unit * length
end
innerImage.Position = UDim2.new(0, relativePosition.x + innerImage.AbsoluteSize.x/2, 0, relativePosition.y + innerImage.AbsoluteSize.y/2)
end
-- input connections
local moveTouchObject = nil
ThumbstickFrame.InputBegan:connect(function(inputObject)
if moveTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch then
return
end
moveTouchObject = inputObject
ThumbstickFrame.Position = UDim2.new(0, inputObject.Position.x - ThumbstickFrame.Size.X.Offset/2, 0, inputObject.Position.y - ThumbstickFrame.Size.Y.Offset/2)
centerPosition = Vector2.new(ThumbstickFrame.AbsolutePosition.x + ThumbstickFrame.AbsoluteSize.x/2,
ThumbstickFrame.AbsolutePosition.y + ThumbstickFrame.AbsoluteSize.y/2)
local direction = Vector2.new(inputObject.Position.x - centerPosition.x, inputObject.Position.y - centerPosition.y)
moveStick(inputObject.Position)
end)
UserInputService.TouchMoved:connect(function(inputObject, isProcessed)
if inputObject == moveTouchObject then
centerPosition = Vector2.new(ThumbstickFrame.AbsolutePosition.x + ThumbstickFrame.AbsoluteSize.x/2,
ThumbstickFrame.AbsolutePosition.y + ThumbstickFrame.AbsoluteSize.y/2)
local direction = Vector2.new(inputObject.Position.x - centerPosition.x, inputObject.Position.y - centerPosition.y)
doMove(direction)
moveStick(inputObject.Position)
end
end)
UserInputService.TouchEnded:connect(function(inputObject, isProcessed)
if inputObject == moveTouchObject then
ThumbstickFrame.Position = position
innerImage.Position = UDim2.new(0, ThumbstickFrame.Size.X.Offset/2 - THUMBSTICK_SIZE/4, 0, ThumbstickFrame.Size.Y.Offset/2 - THUMBSTICK_SIZE/4)
moveTouchObject = nil
if Player then
Player:Move(Vector3.new(), true)
end
end
end)
ThumbstickFrame.Parent = parentFrame
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- DPad Control
--
local DPadFrame = nil
local function setupDPadControls(parentFrame)
if DPadFrame then return end
local position = UDim2.new(0, 10, 1, -230)
DPadFrame = createControlFrame("DPadFrame", position, 192)
local smArrowSize = UDim2.new(0, 23, 0, 23)
local lgArrowSize = UDim2.new(0, 64, 0, 64)
local smImgOffset = Vector2.new(46, 46)
local lgImgOffset = Vector2.new(128, 128)
local bBtn = createArrowLabel("BackButton", DPadFrame, UDim2.new(0.5, -32, 1, -64), lgArrowSize, Vector2.new(0, 0), lgImgOffset)
local fBtn = createArrowLabel("ForwardButton", DPadFrame, UDim2.new(0.5, -32, 0, 0), lgArrowSize, Vector2.new(0, 258), lgImgOffset)
local lBtn = createArrowLabel("LeftButton", DPadFrame, UDim2.new(0, 0, 0.5, -32), lgArrowSize, Vector2.new(129, 129), lgImgOffset)
local rBtn = createArrowLabel("RightButton", DPadFrame, UDim2.new(1, -64, 0.5, -32), lgArrowSize, Vector2.new(0, 129), lgImgOffset)
local jumpBtn = createArrowLabel("JumpButton", DPadFrame, UDim2.new(0.5, -32, 0.5, -32), lgArrowSize, Vector2.new(129, 0), lgImgOffset)
local flBtn = createArrowLabel("ForwardLeftButton", DPadFrame, UDim2.new(0, 35, 0, 35), smArrowSize, Vector2.new(129, 258), smImgOffset)
local frBtn = createArrowLabel("ForwardRightButton", DPadFrame, UDim2.new(1, -55, 0, 35), smArrowSize, Vector2.new(176, 258), smImgOffset)
flBtn.Visible = false
frBtn.Visible = false
-- input connections
local playerJump_func = Player.JumpCharacter
jumpBtn.InputBegan:connect(function(inputObject)
playerJump_func(Player)
end)
local compassDir = {
Vector3.new(1, 0, 0), -- E
Vector3.new(1, 0, 1).unit, -- SE
Vector3.new(0, 0, 1), -- S
Vector3.new(-1, 0, 1).unit, -- SW
Vector3.new(-1, 0, 0), -- W
Vector3.new(-1, 0, -1).unit, -- NW
Vector3.new(0, 0, -1), -- N
Vector3.new(1, 0, -1).unit, -- NE
}
local movementVector = Vector3.new(0, 0, 0)
local function setDirection(inputPosition)
local jumpRadius = jumpBtn.AbsoluteSize.x/2
local centerPos = Vector2.new(DPadFrame.AbsolutePosition.x + DPadFrame.AbsoluteSize.x/2, DPadFrame.AbsolutePosition.y + DPadFrame.AbsoluteSize.y/2)
local direction = Vector2.new(inputPosition.x - centerPos.x, inputPosition.y - centerPos.y)
if direction.magnitude > jumpRadius then
local angle = math.atan2(direction.y, direction.x)
local octant = (math.floor(8 * angle / (2 * math.pi) + 8.5)%8) + 1
movementVector = compassDir[octant]
end
if not flBtn.Visible and movementVector == compassDir[7] then
flBtn.Visible = true
frBtn.Visible = true
end
end
local moveTouchObject = nil
local endStep = 0.1
local function endMoveFunc()
flBtn.Visible = false
frBtn.Visible = false
spawn(function()
while not moveTouchObject and movementVector.magnitude ~= 0 do
local newX = movementVector.x
local newZ = movementVector.y
if movementVector.x > 0 then
newX = movementVector.x - endStep
if newX < 0 then newX = 0 end
elseif movementVector.x < 0 then
newX = movementVector.x + endStep
if newX > 0 then newX = 0 end
end
if movementVector.z > 0 then
newZ = movementVector.z - endStep
if newZ < 0 then newZ = 0 end
elseif movementVector.z < 0 then
newZ = movementVector.z + endStep
if newZ > 0 then newZ = 0 end
end
movementVector = Vector3.new(newX, 0, newZ)
Player:Move(movementVector, true)
wait()
end
if movementVector.magnitude ~= 0 then
movementVector = Vector3.new(0, 0, 0)
Player:Move(movementVector, true)
end
end)
end
DPadFrame.InputBegan:connect(function(inputObject)
if moveTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch then
return
end
moveTouchObject = inputObject
setDirection(inputObject.Position)
Player:Move(movementVector, true)
end)
DPadFrame.InputChanged:connect(function(inputObject)
if inputObject == moveTouchObject then
setDirection(inputObject.Position)
Player:Move(movementVector, true)
end
end)
DPadFrame.InputEnded:connect(function(inputObject)
if inputObject == moveTouchObject then
moveTouchObject = nil
endMoveFunc()
end
end)
DPadFrame.Parent = parentFrame
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- New Thumbpad Controls
--
local ThumbpadFrame = nil
local function setupThumbpad(parentFrame)
if ThumbpadFrame then return end
local isSmallScreen = isSmallScreenDevice()
local position = isSmallScreen and UDim2.new(0, THUMBSTICK_SIZE * 0.5 - 20, 1, -THUMBSTICK_SIZE - 30) or
UDim2.new(0, THUMBSTICK_SIZE/2 - 10, 1, -THUMBSTICK_SIZE * 1.75 - 10)
ThumbpadFrame = createControlFrame("ThumbpadFrame", position, THUMBSTICK_SIZE + 20)
local outerImage = createOuterImage("OuterImage", UDim2.new(0, 10, 0, 10), ThumbpadFrame)
-- arrow set up
local smArrowSize = isSmallScreen and UDim2.new(0, 32, 0, 32) or UDim2.new(0, 64, 0, 64)
local lgArrowSize = UDim2.new(0, smArrowSize.X.Offset * 2, 0, smArrowSize.Y.Offset * 2)
local imgRectSize = Vector2.new(116, 116)
local smImgOffset = isSmallScreen and -4 or -9
local lgImgOffset = isSmallScreen and -28 or -55
local dArrow = createArrowLabel("DownArrow", outerImage, UDim2.new(0.5, -smArrowSize.X.Offset/2, 1, lgImgOffset), smArrowSize, Vector2.new(6, 6), imgRectSize)
local uArrow = createArrowLabel("UpArrow", outerImage, UDim2.new(0.5, -smArrowSize.X.Offset/2, 0, smImgOffset), smArrowSize, Vector2.new(6, 264), imgRectSize)
local lArrow = createArrowLabel("LeftArrow", outerImage, UDim2.new(0, smImgOffset, 0.5, -smArrowSize.Y.Offset/2), smArrowSize, Vector2.new(135, 135), imgRectSize)
local rArrow = createArrowLabel("RightArrow", outerImage, UDim2.new(1, lgImgOffset, 0.5, -smArrowSize.Y.Offset/2), smArrowSize, Vector2.new(6, 135), imgRectSize)
local function doTween(guiObject, endSize, endPosition)
guiObject:TweenSizeAndPosition(endSize, endPosition, Enum.EasingDirection.InOut, Enum.EasingStyle.Linear, 0.15, true)
end
local padOrigin = nil
local deadZone = 0.5
local isRight, isLeft, isUp, isDown = false, false, false, false
local vForward = Vector3.new(0, 0, -1)
local vRight = Vector3.new(1, 0, 0)
local function doMove(pos)
local delta = Vector2.new(pos.x, pos.y) - padOrigin
local inputAxis = delta / (THUMBSTICK_SIZE/2)
-- Scaled Radial Dead Zone
local inputAxisMagnitude = inputAxis.magnitude
if inputAxisMagnitude < deadZone then
inputAxis = Vector3.new(0, 0, 0)
else
inputAxis = inputAxis.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
-- catch possible NAN vector
if inputAxis.magnitude == 0 then
inputAxis = Vector3.new(0, 0, 0)
else
inputAxis = Vector3.new(inputAxis.x, 0, inputAxis.y).unit
end
end
if Player then
Player:Move(inputAxis, true)
end
local forwardDot = inputAxis:Dot(vForward)
local rightDot = inputAxis:Dot(vRight)
if forwardDot > 0.5 then -- UP
if not isUp then
isUp, isDown = true, false
doTween(uArrow, lgArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset, 0, smImgOffset - smArrowSize.Y.Offset * 1.5))
doTween(dArrow, smArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset/2, 1, lgImgOffset))
end
elseif forwardDot < -0.5 then -- DOWN
if not isDown then
isDown, isUp = true, false
doTween(dArrow, lgArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset, 1, lgImgOffset + smArrowSize.Y.Offset/2))
doTween(uArrow, smArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset/2, 0, smImgOffset))
end
else
isUp, isDown = false, false
doTween(dArrow, smArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset/2, 1, lgImgOffset))
doTween(uArrow, smArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset/2, 0, smImgOffset))
end
if rightDot > 0.5 then
if not isRight then
isRight, isLeft = true, false
doTween(rArrow, lgArrowSize, UDim2.new(1, lgImgOffset + smArrowSize.X.Offset/2, 0.5, -smArrowSize.Y.Offset))
doTween(lArrow, smArrowSize, UDim2.new(0, smImgOffset, 0.5, -smArrowSize.Y.Offset/2))
end
elseif rightDot < -0.5 then
if not isLeft then
isLeft, isRight = true, false
doTween(lArrow, lgArrowSize, UDim2.new(0, smImgOffset - smArrowSize.X.Offset * 1.5, 0.5, -smArrowSize.Y.Offset))
doTween(rArrow, smArrowSize, UDim2.new(1, lgImgOffset, 0.5, -smArrowSize.Y.Offset/2))
end
else
isRight, isLeft = false, false
doTween(lArrow, smArrowSize, UDim2.new(0, smImgOffset, 0.5, -smArrowSize.Y.Offset/2))
doTween(rArrow, smArrowSize, UDim2.new(1, lgImgOffset, 0.5, -smArrowSize.Y.Offset/2))
end
end
--input connections
local moveTouchObject = nil
ThumbpadFrame.InputBegan:connect(function(inputObject)
if moveTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch then
return
end
moveTouchObject = inputObject
padOrigin = Vector2.new(ThumbpadFrame.AbsolutePosition.x + ThumbpadFrame.AbsoluteSize.x/2,
ThumbpadFrame.AbsolutePosition.y + ThumbpadFrame.AbsoluteSize.y/2)
doMove(inputObject.Position)
end)
UserInputService.InputChanged:connect(function(inputObject, isProcessed)
if inputObject == moveTouchObject then
doMove(inputObject.Position)
end
end)
UserInputService.InputEnded:connect(function(inputObject)
if inputObject == moveTouchObject then
if Player then
Player:Move(Vector3.new(), true)
end
moveTouchObject = nil
isUp, isDown, isLeft, isRight = false, false, false, false
doTween(dArrow, smArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset/2, 1, lgImgOffset))
doTween(uArrow, smArrowSize, UDim2.new(0.5, -smArrowSize.X.Offset/2, 0, smImgOffset))
doTween(lArrow, smArrowSize, UDim2.new(0, smImgOffset, 0.5, -smArrowSize.Y.Offset/2))
doTween(rArrow, smArrowSize, UDim2.new(1, lgImgOffset, 0.5, -smArrowSize.Y.Offset/2))
end
end)
ThumbpadFrame.Parent = parentFrame
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Movement Mode Setup/Config
--
local function configTouchControls()
if UserInputService.ModalEnabled then
JumpButton.Visible = false
ThumbstickFrame.Visible = false
ThumbpadFrame.Visible = false
DPadFrame.Visible = false
if JumpTouchObject then
JumpTouchObject = nil
end
else
local isThumbstickMode = false
local isThumbpadMode = false
local modeName = GameSettings.TouchMovementMode.Name
-- TODO: Need option from c++, is called "Thumbpad"
if modeName == "Default" or modeName == "Thumbstick" then
isThumbstickMode = true
isThumbpadMode = false
elseif modeName == "Thumbpad" then
isThumbstickMode = false
isThumbpadMode = true
else
isThumbstickMode = false
isThumbpadMode = false
end
JumpButton.Visible = isThumbstickMode or isThumbpadMode
ThumbstickFrame.Visible = isThumbstickMode
ThumbpadFrame.Visible = isThumbpadMode
DPadFrame.Visible = not isThumbstickMode and not isThumbpadMode
end
end
local function setupTouchControls()
local touchControlFrame = Instance.new('Frame')
touchControlFrame.Name = "TouchControlFrame"
touchControlFrame.Size = UDim2.new(1, 0, 1, 0)
touchControlFrame.BackgroundTransparency = 1
touchControlFrame.Parent = RobloxGui
setupJumpButton(touchControlFrame)
setupThumbstick(touchControlFrame)
setupDPadControls(touchControlFrame)
setupThumbpad(touchControlFrame)
UserInputService.Changed:connect(function(property)
if property == "ModalEnabled" then
configTouchControls()
end
end)
configTouchControls()
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Camera Control
--
local CameraTouchObject = nil
local PinchTouchObject = nil
local LastCameraInputPosition = nil
local LastPinchScale = nil
local function onTouchStarted(inputObject, isProcessed)
if isProcessed then return end
if CameraTouchObject then
PinchTouchObject = inputObject
else
CameraTouchObject = inputObject
LastCameraInputPosition = inputObject.Position
end
end
local function onTouchMoved(inputObject, isProcessed)
-- pinch zoom
if CameraTouchObject and PinchTouchObject then
if not LastPinchScale then
if inputObject == CameraTouchObject then
LastPinchScale = (inputObject.Position - PinchTouchObject.Position).magnitude
elseif inputObject == PinchTouchObject then
LastPinchScale = (inputObject.Position - CameraTouchObject.Position).magnitude
end
else
local newPinchScale = 0
if inputObject == CameraTouchObject then
newPinchScale = (inputObject.Position - PinchTouchObject.Position).magnitude
elseif inputObject == PinchTouchObject then
newPinchScale = (inputObject.Position - CameraTouchObject.Position).magnitude
end
if newPinchScale ~= 0 then
local delta = newPinchScale - LastPinchScale
if delta ~= 0 then
UserInputService:ZoomCamera(delta * CAMERA_ZOOM_SENSITIVITY)
end
LastPinchScale = newPinchScale
end
end
-- camera pan
elseif inputObject == CameraTouchObject then
local currentPosition = inputObject.Position
if LastCameraInputPosition then
local touchDelta = (LastCameraInputPosition - currentPosition) * CAMERA_ROTATE_SENSITIVITY
UserInputService:RotateCamera(Vector2.new(touchDelta.x, touchDelta.y))
end
LastCameraInputPosition = currentPosition
end
end
local function onTouchEnded(inputObject, isProcessed)
if inputObject == CameraTouchObject then
-- swap input objects
if PinchTouchObject then
CameraTouchObject = PinchTouchObject
LastCameraInputPosition = CameraTouchObject.Position
PinchTouchObject = nil
LastPinchScale = nil
else
CameraTouchObject = nil
LastCameraInputPosition = nil
end
elseif inputObject == PinchTouchObject then
if CameraTouchObject then
LastCameraInputPosition = CameraTouchObject.Position
end
PinchTouchObject = nil
LastPinchScale = nil
end
end
UserInputService.TouchStarted:connect(onTouchStarted)
UserInputService.TouchMoved:connect(onTouchMoved)
UserInputService.TouchEnded:connect(onTouchEnded)
if UserInputService:IsLuaTouchControls() then
setupTouchControls()
else
script:Destroy()
end
GameSettings.Changed:connect(function(property)
if property == "TouchMovementMode" then
configTouchControls()
end
end)
else -- OLD TOUCH CONTROLS WITH THUMBPAD
-- obligatory stuff to make sure we don't access nil data
while not Game do
wait()
end
while not Game:FindFirstChild("Players") do
wait()
end
while not Game:GetService("Players").LocalPlayer do
wait()
end
while not Game:FindFirstChild("CoreGui") do
wait()
end
while not Game:GetService("CoreGui"):FindFirstChild("RobloxGui") do
wait()
end
local userInputService = Game:GetService("UserInputService")
local success = pcall(function() userInputService:IsLuaTouchControls() end)
if not success then
script:Destroy()
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Variables
local screenResolution = Game:GetService("GuiService"):GetScreenResolution()
function isSmallScreenDevice()
return screenResolution.y <= 500
end
local GameSettings = UserSettings().GameSettings
local localPlayer = Game:GetService("Players").LocalPlayer
local isInThumbstickMode = false
local thumbstickInactiveAlpha = 0.3
local thumbstickSize = 120
if isSmallScreenDevice() then
thumbstickSize = 70
end
local touchControlsSheet = "rbxasset://textures/ui/TouchControlsSheet.png"
local DPadSheet = "rbxasset://textures/ui/DPadSheet.png"
local ThumbstickDeadZone = 5
local ThumbstickMaxPercentGive = 0.92
local thumbstickTouches = {}
local jumpButtonSize = 90
if isSmallScreenDevice() then
jumpButtonSize = 70
end
local oldJumpTouches = {}
local currentJumpTouch = nil
local CameraRotateSensitivity = 0.007
local CameraRotateDeadZone = CameraRotateSensitivity * 16
local CameraZoomSensitivity = 0.03
local PinchZoomDelay = 0.2
local cameraTouch = nil
local dpadTouch = nil
local thumbpadTouchObject = nil
-- make sure all of our images are good to go
Game:GetService("ContentProvider"):Preload(touchControlsSheet)
Game:GetService("ContentProvider"):Preload(DPadSheet)
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Functions
local isPointInRect = function(point, rectPos, rectSize)
if point.x >= rectPos.x and point.x <= (rectPos.x + rectSize.x) then
if point.y >= rectPos.y and point.y <= (rectPos.y + rectSize.y) then
return true
end
end
return false
end
--
-- Thumbstick Control
--
function setCameraTouch(newTouch)
cameraTouch = newTouch
if newTouch == nil then
pcall(function() userInputService.InCameraGesture = false end)
else
pcall(function() userInputService.InCameraGesture = true end)
end
end
function DistanceBetweenTwoPoints(point1, point2)
local dx = point2.x - point1.x
local dy = point2.y - point1.y
return math.sqrt( (dx*dx) + (dy*dy) )
end
function transformFromCenterToTopLeft(pointToTranslate, guiObject)
return UDim2.new(0,pointToTranslate.x - guiObject.AbsoluteSize.x/2,0,pointToTranslate.y - guiObject.AbsoluteSize.y/2)
end
function rotatePointAboutLocation(pointToRotate, pointToRotateAbout, radians)
local sinAnglePercent = math.sin(radians)
local cosAnglePercent = math.cos(radians)
local transformedPoint = pointToRotate
-- translate point back to origin:
transformedPoint = Vector2.new(transformedPoint.x - pointToRotateAbout.x, transformedPoint.y - pointToRotateAbout.y)
-- rotate point
local xNew = transformedPoint.x * cosAnglePercent - transformedPoint.y * sinAnglePercent
local yNew = transformedPoint.x * sinAnglePercent + transformedPoint.y * cosAnglePercent
-- translate point back:
transformedPoint = Vector2.new(xNew + pointToRotateAbout.x, yNew + pointToRotateAbout.y)
return transformedPoint
end
function dotProduct(v1,v2)
return ((v1.x*v2.x) + (v1.y*v2.y))
end
function stationaryThumbstickTouchMove(thumbstickFrame, thumbstickOuter, touchLocation)
local thumbstickOuterCenterPosition = Vector2.new(thumbstickOuter.Position.X.Offset + thumbstickOuter.AbsoluteSize.x/2, thumbstickOuter.Position.Y.Offset + thumbstickOuter.AbsoluteSize.y/2)
local centerDiff = DistanceBetweenTwoPoints(touchLocation, thumbstickOuterCenterPosition)
-- thumbstick is moving outside our region, need to cap its distance
if centerDiff > (thumbstickSize/2) then
local thumbVector = Vector2.new(touchLocation.x - thumbstickOuterCenterPosition.x,touchLocation.y - thumbstickOuterCenterPosition.y);
local normal = thumbVector.unit
if normal.x == math.nan or normal.x == math.inf then
normal = Vector2.new(0,normal.y)
end
if normal.y == math.nan or normal.y == math.inf then
normal = Vector2.new(normal.x,0)
end
local newThumbstickInnerPosition = thumbstickOuterCenterPosition + (normal * (thumbstickSize/2))
thumbstickFrame.Position = transformFromCenterToTopLeft(newThumbstickInnerPosition, thumbstickFrame)
else
thumbstickFrame.Position = transformFromCenterToTopLeft(touchLocation,thumbstickFrame)
end
return Vector2.new(thumbstickFrame.Position.X.Offset - thumbstickOuter.Position.X.Offset,thumbstickFrame.Position.Y.Offset - thumbstickOuter.Position.Y.Offset)
end
function followThumbstickTouchMove(thumbstickFrame, thumbstickOuter, touchLocation)
local thumbstickOuterCenter = Vector2.new(thumbstickOuter.Position.X.Offset + thumbstickOuter.AbsoluteSize.x/2, thumbstickOuter.Position.Y.Offset + thumbstickOuter.AbsoluteSize.y/2)
-- thumbstick is moving outside our region, need to position outer thumbstick texture carefully (to make look and feel like actual joystick controller)
if DistanceBetweenTwoPoints(touchLocation, thumbstickOuterCenter) > thumbstickSize/2 then
local thumbstickInnerCenter = Vector2.new(thumbstickFrame.Position.X.Offset + thumbstickFrame.AbsoluteSize.x/2, thumbstickFrame.Position.Y.Offset + thumbstickFrame.AbsoluteSize.y/2)
local movementVectorUnit = Vector2.new(touchLocation.x - thumbstickInnerCenter.x, touchLocation.y - thumbstickInnerCenter.y).unit
local outerToInnerVectorCurrent = Vector2.new(thumbstickInnerCenter.x - thumbstickOuterCenter.x, thumbstickInnerCenter.y - thumbstickOuterCenter.y)
local outerToInnerVectorCurrentUnit = outerToInnerVectorCurrent.unit
local movementVector = Vector2.new(touchLocation.x - thumbstickInnerCenter.x, touchLocation.y - thumbstickInnerCenter.y)
-- First, find the angle between the new thumbstick movement vector,
-- and the vector between thumbstick inner and thumbstick outer.
-- We will use this to pivot thumbstick outer around thumbstick inner, gives a nice joystick feel
local crossOuterToInnerWithMovement = (outerToInnerVectorCurrentUnit.x * movementVectorUnit.y) - (outerToInnerVectorCurrentUnit.y * movementVectorUnit.x)
local angle = math.atan2(crossOuterToInnerWithMovement, dotProduct(outerToInnerVectorCurrentUnit, movementVectorUnit))
local anglePercent = angle * math.min( (movementVector.magnitude)/(outerToInnerVectorCurrent.magnitude), 1.0);
-- If angle is significant, rotate about the inner thumbsticks current center
if math.abs(anglePercent) > 0.00001 then
local outerThumbCenter = rotatePointAboutLocation(thumbstickOuterCenter, thumbstickInnerCenter, anglePercent)
thumbstickOuter.Position = transformFromCenterToTopLeft(Vector2.new(outerThumbCenter.x,outerThumbCenter.y), thumbstickOuter)
end
-- now just translate outer thumbstick to make sure it stays nears inner thumbstick
thumbstickOuter.Position = UDim2.new(0,thumbstickOuter.Position.X.Offset+movementVector.x,0,thumbstickOuter.Position.Y.Offset+movementVector.y)
end
thumbstickFrame.Position = transformFromCenterToTopLeft(touchLocation,thumbstickFrame)
-- a bit of error checking to make sure thumbsticks stay close to eachother
thumbstickFramePosition = Vector2.new(thumbstickFrame.Position.X.Offset,thumbstickFrame.Position.Y.Offset)
thumbstickOuterPosition = Vector2.new(thumbstickOuter.Position.X.Offset,thumbstickOuter.Position.Y.Offset)
if DistanceBetweenTwoPoints(thumbstickFramePosition, thumbstickOuterPosition) > thumbstickSize/2 then
local vectorWithLength = (thumbstickOuterPosition - thumbstickFramePosition).unit * thumbstickSize/2
thumbstickOuter.Position = UDim2.new(0,thumbstickFramePosition.x + vectorWithLength.x,0,thumbstickFramePosition.y + vectorWithLength.y)
end
return Vector2.new(thumbstickFrame.Position.X.Offset - thumbstickOuter.Position.X.Offset,thumbstickFrame.Position.Y.Offset - thumbstickOuter.Position.Y.Offset)
end
function movementOutsideDeadZone(movementVector)
return ( (math.abs(movementVector.x) > ThumbstickDeadZone) or (math.abs(movementVector.y) > ThumbstickDeadZone) )
end
function constructThumbstick(defaultThumbstickPos, updateFunction, stationaryThumbstick)
local thumbstickFrame = Instance.new("Frame")
thumbstickFrame.Name = "ThumbstickFrame"
thumbstickFrame.Active = true
thumbstickFrame.Size = UDim2.new(0,thumbstickSize,0,thumbstickSize)
thumbstickFrame.Position = defaultThumbstickPos
thumbstickFrame.BackgroundTransparency = 1
local outerThumbstick = Instance.new("ImageLabel")
outerThumbstick.Name = "OuterThumbstick"
outerThumbstick.Image = touchControlsSheet
outerThumbstick.ImageRectOffset = Vector2.new(0,0)
outerThumbstick.ImageRectSize = Vector2.new(220,220)
outerThumbstick.BackgroundTransparency = 1
outerThumbstick.Size = UDim2.new(0,thumbstickSize,0,thumbstickSize)
outerThumbstick.Position = defaultThumbstickPos
outerThumbstick.Parent = Game:GetService("CoreGui").RobloxGui
local innerThumbstick = Instance.new("ImageLabel")
innerThumbstick.Name = "InnerThumbstick"
innerThumbstick.Image = touchControlsSheet
innerThumbstick.ImageRectOffset = Vector2.new(220,0)
innerThumbstick.ImageRectSize = Vector2.new(111,111)
innerThumbstick.BackgroundTransparency = 1
innerThumbstick.Size = UDim2.new(0,thumbstickSize/2,0,thumbstickSize/2)
innerThumbstick.Position = UDim2.new(0, thumbstickFrame.Size.X.Offset/2 - thumbstickSize/4, 0, thumbstickFrame.Size.Y.Offset/2 - thumbstickSize/4)
innerThumbstick.Parent = thumbstickFrame
innerThumbstick.ZIndex = 2
local thumbstickTouch = nil
local userInputServiceTouchMovedCon = nil
local userInputSeviceTouchEndedCon = nil
local startInputTracking = function(inputObject)
if thumbstickTouch then return end
if inputObject == cameraTouch then return end
if inputObject == currentJumpTouch then return end
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
thumbstickTouch = inputObject
table.insert(thumbstickTouches,thumbstickTouch)
thumbstickFrame.Position = transformFromCenterToTopLeft(thumbstickTouch.Position,thumbstickFrame)
outerThumbstick.Position = thumbstickFrame.Position
userInputServiceTouchMovedCon = userInputService.TouchMoved:connect(function(movedInput)
if movedInput == thumbstickTouch then
local movementVector = nil
if stationaryThumbstick then
movementVector = stationaryThumbstickTouchMove(thumbstickFrame,outerThumbstick,Vector2.new(movedInput.Position.x,movedInput.Position.y))
else
movementVector = followThumbstickTouchMove(thumbstickFrame,outerThumbstick,Vector2.new(movedInput.Position.x,movedInput.Position.y))
end
if updateFunction then
updateFunction(movementVector,outerThumbstick.Size.X.Offset/2)
end
end
end)
userInputSeviceTouchEndedCon = userInputService.TouchEnded:connect(function(endedInput)
if endedInput == thumbstickTouch then
if updateFunction then
updateFunction(Vector2.new(0,0),1)
end
userInputSeviceTouchEndedCon:disconnect()
userInputServiceTouchMovedCon:disconnect()
thumbstickFrame.Position = defaultThumbstickPos
outerThumbstick.Position = defaultThumbstickPos
for i, object in pairs(thumbstickTouches) do
if object == thumbstickTouch then
table.remove(thumbstickTouches,i)
break
end
end
thumbstickTouch = nil
end
end)
end
thumbstickFrame.Visible = not userInputService.ModalEnabled
outerThumbstick.Visible = not userInputService.ModalEnabled
thumbstickFrame.InputBegan:connect(startInputTracking)
return thumbstickFrame, outerThumbstick
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- DPad Control
--
function createDPadArrowButton(name, parent, position, size, image, spriteOffset, spriteSize)
local DPadArrow = Instance.new("ImageButton")
DPadArrow.Name = name
DPadArrow.Image = image
DPadArrow.ImageRectOffset = spriteOffset
DPadArrow.ImageRectSize = spriteSize
DPadArrow.BackgroundTransparency = 1
DPadArrow.Size = size
DPadArrow.Position = position
DPadArrow.Parent = parent
return DPadArrow
end
function createDPad()
local DPadFrame = Instance.new("Frame")
DPadFrame.Name = "DPadFrame"
DPadFrame.Active = true
DPadFrame.Size = UDim2.new(0,192,0,192)
DPadFrame.Position = UDim2.new(0,10,1,-230)
DPadFrame.BackgroundTransparency = 1
-- local image = "rbxassetid://133293265"
local image = DPadSheet
local bigSize = UDim2.new(0,64,0,64)
local smallSize = UDim2.new(0,23,0,23)
local spriteSizeLarge = Vector2.new(128, 128)
local spriteSizeSmall = Vector2.new(46, 46)
createDPadArrowButton("BackButton", DPadFrame, UDim2.new(0.5, -32, 1, -64), bigSize, image, Vector2.new(0, 0), spriteSizeLarge)
createDPadArrowButton("ForwardButton", DPadFrame, UDim2.new(0.5, -32, 0, 0), bigSize, image, Vector2.new(0, 258), spriteSizeLarge)
createDPadArrowButton("JumpButton", DPadFrame, UDim2.new(0.5, -32, 0.5, -32), bigSize, image, Vector2.new(129, 0), spriteSizeLarge)
createDPadArrowButton("LeftButton", DPadFrame, UDim2.new(0, 0, 0.5, -32), bigSize, image, Vector2.new(129,129), spriteSizeLarge)
createDPadArrowButton("RightButton", DPadFrame, UDim2.new(1, -64, 0.5, -32), bigSize, image, Vector2.new(0, 129), spriteSizeLarge)
createDPadArrowButton("forwardLeftButton", DPadFrame, UDim2.new(0, 35, 0, 35), smallSize, image, Vector2.new(129,258), spriteSizeSmall)
createDPadArrowButton("forwardRightButton", DPadFrame, UDim2.new(1, -55, 0, 35), smallSize, image, Vector2.new(176,258), spriteSizeSmall)
return DPadFrame
end
function setupDPadControls(DPadFrame)
local moveCharacterFunc = localPlayer.MoveCharacter
DPadFrame.JumpButton.InputBegan:connect(function(inputObject)
localPlayer:JumpCharacter()
end)
local movementVector = Vector2.new(0,0)
function setupButton(button,funcToCallBegin,funcToCallEnd)
button.InputBegan:connect(function(inputObject)
if not dpadTouch and inputObject.UserInputType == Enum.UserInputType.Touch and inputObject.UserInputState == Enum.UserInputState.Begin then
dpadTouch = inputObject
funcToCallBegin()
end
end)
button.InputEnded:connect(function(inputObject)
if dpadTouch == inputObject then
if funcToCallEnd then
funcToCallEnd()
end
end
end)
end
local forwardButtonBegin = function()
movementVector = Vector2.new(0,-1)
moveCharacterFunc(localPlayer, Vector2.new(0,-1), 1)
DPadFrame.forwardLeftButton.Visible = true
DPadFrame.forwardRightButton.Visible = true
end
local backwardButtonBegin = function()
movementVector = Vector2.new(0,1)
moveCharacterFunc(localPlayer, Vector2.new(0,1),1)
end
local leftButtonBegin = function()
movementVector = Vector2.new(-1,0)
moveCharacterFunc(localPlayer, Vector2.new(-1,0),1)
end
local rightButtonBegin = function()
movementVector = Vector2.new(1,0)
moveCharacterFunc(localPlayer, Vector2.new(1,0),1)
end
DPadFrame.InputEnded:connect(function()
DPadFrame.forwardLeftButton.Visible = false
DPadFrame.forwardRightButton.Visible = false
end)
local endStep = 0.08
local endMovementFunc = function()
DPadFrame.forwardLeftButton.Visible = false
DPadFrame.forwardRightButton.Visible = false
Spawn(function()
while not dpadTouch and movementVector ~= Vector2.new(0,0) do
local newX = movementVector.x
local newY = movementVector.y
if movementVector.x > 0 then
newX = movementVector.x - endStep
if newX < 0 then newX = 0 end
elseif movementVector.x < 0 then
newX = movementVector.x + endStep
if newX > 0 then newX = 0 end
end
if movementVector.y > 0 then
newY = movementVector.y - endStep
if newY < 0 then newY = 0 end
elseif movementVector.y < 0 then
newY = movementVector.y + endStep
if newY > 0 then newY = 0 end
end
movementVector = Vector2.new(newX,newY)
moveCharacterFunc(localPlayer, movementVector,1)
wait(1/60)
end
if movementVector ~= Vector2.new(0,0) then
movementVector = Vector2.new(0,0)
moveCharacterFunc(localPlayer,Vector2.new(0,0) ,0)
end
end)
end
local removeDiagonalButtons = function()
if isPointInRect(dpadTouch.Position,DPadFrame.ForwardButton.AbsolutePosition,DPadFrame.ForwardButton.AbsoluteSize) then
DPadFrame.forwardLeftButton.Visible = false
DPadFrame.forwardRightButton.Visible = false
end
end
setupButton(DPadFrame.ForwardButton,forwardButtonBegin,removeDiagonalButtons)
setupButton(DPadFrame.BackButton,backwardButtonBegin,nil)
setupButton(DPadFrame.LeftButton,leftButtonBegin,nil)
setupButton(DPadFrame.RightButton,rightButtonBegin,nil)
local getMovementVector = function(touchPosition)
local xDiff = touchPosition.x - (DPadFrame.AbsolutePosition.x + DPadFrame.AbsoluteSize.x/2)
local yDiff = touchPosition.y - (DPadFrame.AbsolutePosition.y + DPadFrame.AbsoluteSize.y/2)
local vectorNew = Vector2.new(xDiff,yDiff)
movementVector = vectorNew.unit
return vectorNew.unit
end
Game:GetService("UserInputService").TouchMoved:connect(function(touchObject)
if touchObject == dpadTouch then
if isPointInRect(dpadTouch.Position,DPadFrame.AbsolutePosition,DPadFrame.AbsoluteSize) then
moveCharacterFunc(localPlayer, getMovementVector(dpadTouch.Position),1)
else
endMovementFunc()
end
end
end)
Game:GetService("UserInputService").TouchEnded:connect(function(touchObject)
if touchObject == dpadTouch then
dpadTouch = nil
endMovementFunc()
end
end)
DPadFrame.Visible = not userInputService.ModalEnabled
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Character Movement
--
local characterThumbstick = nil
local characterOuterThumbstick = nil
function setupCharacterMovement( parentFrame )
local lastMovementVector, lastMaxMovement = nil
local moveCharacterFunc = localPlayer.MoveCharacter
local moveCharacterFunction = function ( movementVector, maxMovement )
if localPlayer then
if movementOutsideDeadZone(movementVector) then
lastMovementVector = movementVector
lastMaxMovement = maxMovement
-- sometimes rounding error will not allow us to go max speed at some
-- thumbstick angles, fix this with a bit of fudging near 100% throttle
if movementVector.magnitude/maxMovement > ThumbstickMaxPercentGive then
maxMovement = movementVector.magnitude - 1
end
moveCharacterFunc(localPlayer, movementVector, maxMovement)
else
lastMovementVector = Vector2.new(0,0)
lastMaxMovement = 1
moveCharacterFunc(localPlayer, lastMovementVector, lastMaxMovement)
end
end
end
local thumbstickPos = UDim2.new(0,thumbstickSize/2,1,-thumbstickSize*1.75)
if isSmallScreenDevice() then
thumbstickPos = UDim2.new(0,(thumbstickSize/2) - 10,1,-thumbstickSize - 20)
end
characterThumbstick, characterOuterThumbstick = constructThumbstick(thumbstickPos, moveCharacterFunction, false)
characterThumbstick.Name = "CharacterThumbstick"
characterThumbstick.Parent = parentFrame
local refreshCharacterMovement = function()
if localPlayer and isInThumbstickMode and moveCharacterFunc and lastMovementVector and lastMaxMovement then
moveCharacterFunc(localPlayer, lastMovementVector, lastMaxMovement)
end
end
return refreshCharacterMovement
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Jump Button
--
local jumpButton = nil
function setupJumpButton( parentFrame )
if (jumpButton == nil) then
jumpButton = Instance.new("ImageButton")
jumpButton.Name = "JumpButton"
jumpButton.BackgroundTransparency = 1
jumpButton.Image = touchControlsSheet
jumpButton.ImageRectOffset = Vector2.new(176,222)
jumpButton.ImageRectSize = Vector2.new(174,174)
jumpButton.Size = UDim2.new(0,jumpButtonSize,0,jumpButtonSize)
if isSmallScreenDevice() then
jumpButton.Position = UDim2.new(1, -(jumpButtonSize*2.25), 1, -jumpButtonSize - 20)
else
jumpButton.Position = UDim2.new(1, -(jumpButtonSize*2.75), 1, -jumpButtonSize - 120)
end
local playerJumpFunc = localPlayer.JumpCharacter
local doJumpLoop = function ()
while currentJumpTouch do
if localPlayer then
playerJumpFunc(localPlayer)
end
wait(1/60)
end
end
jumpButton.InputBegan:connect(function(inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
if currentJumpTouch then return end
if inputObject == cameraTouch then return end
for i, touch in pairs(oldJumpTouches) do
if touch == inputObject then
return
end
end
currentJumpTouch = inputObject
jumpButton.ImageRectOffset = Vector2.new(0,222)
jumpButton.ImageRectSize = Vector2.new(174,174)
doJumpLoop()
end)
jumpButton.InputEnded:connect(function (inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
jumpButton.ImageRectOffset = Vector2.new(176,222)
jumpButton.ImageRectSize = Vector2.new(174,174)
if inputObject == currentJumpTouch then
table.insert(oldJumpTouches,currentJumpTouch)
currentJumpTouch = nil
end
end)
userInputService.InputEnded:connect(function ( globalInputObject )
for i, touch in pairs(oldJumpTouches) do
if touch == globalInputObject then
table.remove(oldJumpTouches,i)
break
end
end
end)
jumpButton.Parent = parentFrame
end
jumpButton.Visible = not userInputService.ModalEnabled
end
function isTouchUsedByJumpButton( touch )
if touch == currentJumpTouch then return true end
for i, touchToCompare in pairs(oldJumpTouches) do
if touch == touchToCompare then
return true
end
end
return false
end
function isTouchUsedByThumbstick(touch)
for i, touchToCompare in pairs(thumbstickTouches) do
if touch == touchToCompare then
return true
end
end
return false
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Camera Control
--
function setupCameraControl(parentFrame, refreshCharacterMoveFunc)
local lastPos = nil
local hasRotatedCamera = false
local rotateCameraFunc = userInputService.RotateCamera
local pinchTime = -1
local shouldPinch = false
local lastPinchScale = nil
local zoomCameraFunc = userInputService.ZoomCamera
local pinchTouches = {}
local pinchFrame = nil
local pinchInputChangedCon = nil
local pinchInputEndedCon = nil
local resetCameraRotateState = function()
setCameraTouch(nil)
hasRotatedCamera = false
lastPos = nil
end
local resetPinchState = function ()
pinchTouches = {}
pinchTime = -1
lastPinchScale = nil
shouldPinch = false
pinchFrame:Destroy()
pinchFrame = nil
end
local startPinch = function(firstTouch, secondTouch)
-- track pinching in new frame
if pinchFrame then pinchFrame:Destroy() end -- make sure we didn't track in any mud
pinchFrame = Instance.new("Frame")
pinchFrame.Name = "PinchFrame"
pinchFrame.BackgroundTransparency = 1
pinchFrame.Parent = parentFrame
pinchFrame.Size = UDim2.new(1,0,1,0)
pinchFrame.InputChanged:connect(function(inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
if not shouldPinch then
resetPinchState()
return
end
if lastPinchScale == nil then -- first pinch move, just set up scale
if inputObject == firstTouch then
lastPinchScale = (inputObject.Position - secondTouch.Position).magnitude
firstTouch = inputObject
elseif inputObject == secondTouch then
lastPinchScale = (inputObject.Position - firstTouch.Position).magnitude
secondTouch = inputObject
end
else -- we are now actually pinching, do comparison to last pinch size
local newPinchDistance = 0
if inputObject == firstTouch then
newPinchDistance = (inputObject.Position - secondTouch.Position).magnitude
firstTouch = inputObject
elseif inputObject == secondTouch then
newPinchDistance = (inputObject.Position - firstTouch.Position).magnitude
secondTouch = inputObject
end
if newPinchDistance ~= 0 then
local pinchDiff = newPinchDistance - lastPinchScale
if pinchDiff ~= 0 then
zoomCameraFunc(userInputService, (pinchDiff * CameraZoomSensitivity))
end
lastPinchScale = newPinchDistance
end
end
end)
pinchFrame.InputEnded:connect(function(inputObject) -- pinch is over, destroy all
if inputObject == firstTouch or inputObject == secondTouch then
resetPinchState()
end
end)
end
local pinchGestureReceivedTouch = function(inputObject)
if #pinchTouches < 1 then
table.insert(pinchTouches,inputObject)
pinchTime = tick()
shouldPinch = false
elseif #pinchTouches == 1 then
shouldPinch = ( (tick() - pinchTime) <= PinchZoomDelay )
if shouldPinch then
table.insert(pinchTouches,inputObject)
startPinch(pinchTouches[1], pinchTouches[2])
resetCameraRotateState()
return true
else -- shouldn't ever get here, but just in case
pinchTouches = {}
end
end
return false
end
parentFrame.InputBegan:connect(function (inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
if isTouchUsedByJumpButton(inputObject) then return end
if thumbpadTouchObject == inputObject then return end
local usedByThumbstick = isTouchUsedByThumbstick(inputObject)
local isPinching = false
if not usedByThumbstick then
isPinching = pinchGestureReceivedTouch(inputObject)
end
if cameraTouch == nil and not usedByThumbstick and not isPinching then
setCameraTouch(inputObject)
lastPos = Vector2.new(cameraTouch.Position.x,cameraTouch.Position.y)
lastTick = tick()
end
end)
userInputService.InputChanged:connect(function (inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch then return end
if cameraTouch ~= inputObject then return end
local newPos = Vector2.new(cameraTouch.Position.x,cameraTouch.Position.y)
local touchDiff = Vector2.new(0,0)
if lastPos then
touchDiff = (lastPos - newPos) * CameraRotateSensitivity
end
-- first time rotating outside deadzone, just setup for next changed event
if not hasRotatedCamera and (touchDiff.magnitude > CameraRotateDeadZone) then
hasRotatedCamera = true
lastPos = newPos
end
-- fire everytime after we have rotated out of deadzone
if hasRotatedCamera and (lastPos ~= newPos) then
rotateCameraFunc(userInputService, touchDiff)
refreshCharacterMoveFunc()
lastPos = newPos
end
end)
userInputService.InputEnded:connect(function (inputObject)
if cameraTouch == inputObject or cameraTouch == nil then
resetCameraRotateState()
end
for i, touch in pairs(pinchTouches) do
if touch == inputObject then
table.remove(pinchTouches,i)
end
end
end)
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Thumbpad -- Jason
--
local thumbpadFrame = nil
function setupThumbpad(parentFrame)
local isSmallScreen = isSmallScreenDevice()
local position = isSmallScreen and UDim2.new(0, thumbstickSize * 0.5 - 20, 1, -thumbstickSize - 30) or
UDim2.new(0, thumbstickSize/2 - 10, 1, -thumbstickSize * 1.75 - 10)
thumbpadFrame = Instance.new('Frame') -- container
thumbpadFrame.Name = "NewThumbstickFrame"
thumbpadFrame.Active = true
thumbpadFrame.Size = UDim2.new(0, thumbstickSize + 20, 0, thumbstickSize + 20)
thumbpadFrame.Position = position
thumbpadFrame.BackgroundTransparency = 1
local outerStickFrame = Instance.new('ImageLabel') -- circle image
outerStickFrame.Name = "OuterImage"
outerStickFrame.Image = touchControlsSheet
outerStickFrame.ImageRectOffset = Vector2.new()
outerStickFrame.ImageRectSize = Vector2.new(220, 220)
outerStickFrame.BackgroundTransparency = 1
outerStickFrame.Size = UDim2.new(0, thumbstickSize, 0, thumbstickSize)
outerStickFrame.Position = UDim2.new(0, 10, 0, 10)
outerStickFrame.Parent = thumbpadFrame
-- arrow set up
local arrowSize = isSmallScreen and UDim2.new(0, 32, 0, 32) or UDim2.new(0, 64, 0, 64)
local tweenSize = UDim2.new(0, arrowSize.X.Offset * 2, 0, arrowSize.Y.Offset * 2)
local rectSize = Vector2.new(116, 116)
local smallOffset = isSmallScreen and -4 or -9
local largeOffset = isSmallScreen and -28 or -55
local function createArrow(name, position, size, offset)
local arrow = Instance.new('ImageLabel')
arrow.Name = name
arrow.Image = DPadSheet
arrow.ImageRectOffset = offset
arrow.ImageRectSize = rectSize
arrow.BackgroundTransparency = 1
arrow.Size = size
arrow.Position = position
arrow.Parent = outerStickFrame
return arrow
end
--
local dArrow = createArrow("DownImage", UDim2.new(0.5, -arrowSize.X.Offset/2, 1, largeOffset), arrowSize, Vector2.new(6, 6))
local uArrow = createArrow("UpImage", UDim2.new(0.5, -arrowSize.X.Offset/2, 0, smallOffset), arrowSize, Vector2.new(6, 264))
local lArrow = createArrow("LeftImage", UDim2.new(0, smallOffset, 0.5, -arrowSize.Y.Offset/2), arrowSize, Vector2.new(135, 135))
local rArrow = createArrow("RightImage", UDim2.new(1, largeOffset, 0.5, -arrowSize.Y.Offset/2), arrowSize, Vector2.new(6, 135))
local function doTween(guiObject, endSize, endPosition)
guiObject:TweenSizeAndPosition(endSize, endPosition, Enum.EasingDirection.InOut,
Enum.EasingStyle.Linear, 0.15, true)
end
local padOrigin = nil
local deadZone = 0.5
local isRight, isLeft, isUp, isDown = false, false, false, false
local function move(pos)
local delta = Vector2.new(pos.x, pos.y) - padOrigin
local inputAxis = delta / (thumbstickSize/2)
-- Dead Zone
local inputAxisMagnitude = inputAxis.magnitude
if inputAxisMagnitude < deadZone then
inputAxis = Vector3.new(0, 0, 0)
else
inputAxis = inputAxis.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
-- catch possible NAN Vector
if inputAxis.magnitude == 0 then
inputAxis = Vector3.new(0, 0, 0)
else
inputAxis = Vector3.new(inputAxis.x, 0, inputAxis.y).unit
end
end
localPlayer:Move(inputAxis, true)
-- arrow tweening
local forwardDot = inputAxis:Dot(Vector3.new(0, 0, -1))
local rightDot = inputAxis:Dot(Vector3.new(1, 0, 0))
if forwardDot > 0.5 then -- UP
if not isUp then
isUp = true
isDown = false
doTween(uArrow, tweenSize, UDim2.new(0.5, -arrowSize.X.Offset, 0, smallOffset - arrowSize.Y.Offset * 1.5))
doTween(dArrow, arrowSize, UDim2.new(0.5, -arrowSize.X.Offset/2, 1, largeOffset))
end
elseif forwardDot < -0.5 then -- DOWN
if not isDown then
isDown = true
isUp = false
doTween(dArrow, tweenSize, UDim2.new(0.5, -arrowSize.X.Offset, 1, largeOffset + arrowSize.Y.Offset/2))
doTween(uArrow, arrowSize, UDim2.new(0.5, -arrowSize.X.Offset/2, 0, smallOffset))
end
else
isDown, isUp = false, false
doTween(dArrow, arrowSize, UDim2.new(0.5, -arrowSize.X.Offset/2, 1, largeOffset))
doTween(uArrow, arrowSize, UDim2.new(0.5, -arrowSize.X.Offset/2, 0, smallOffset))
end
if rightDot > 0.5 then -- RIGHT
if not isRight then
isRight = true
isLeft = false
doTween(rArrow, tweenSize, UDim2.new(1, largeOffset + arrowSize.X.Offset/2, 0.5, -arrowSize.Y.Offset))
doTween(lArrow, arrowSize, UDim2.new(0, smallOffset, 0.5, -arrowSize.Y.Offset/2))
end
elseif rightDot < -0.5 then -- LEFT
if not isLeft then
isLeft = true
isRight = false
doTween(lArrow, tweenSize, UDim2.new(0, smallOffset - arrowSize.X.Offset * 1.5, 0.5, -arrowSize.Y.Offset))
doTween(rArrow, arrowSize, UDim2.new(1, largeOffset, 0.5, -arrowSize.Y.Offset/2))
end
else
isRight, isLeft = false, false
doTween(lArrow, arrowSize, UDim2.new(0, smallOffset, 0.5, -arrowSize.Y.Offset/2))
doTween(rArrow, arrowSize, UDim2.new(1, largeOffset, 0.5, -arrowSize.Y.Offset/2))
end
end
-- input connections
thumbpadFrame.InputBegan:connect(function(inputObject)
if thumbpadTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch then
return
end
thumbpadTouchObject = inputObject
padOrigin = Vector2.new(thumbpadFrame.AbsolutePosition.x + thumbpadFrame.AbsoluteSize.x/2,
thumbpadFrame.AbsolutePosition.y + thumbpadFrame.AbsoluteSize.y/2)
move(inputObject.Position)
end)
userInputService.InputChanged:connect(function(inputObject, isProcessed)
if inputObject == thumbpadTouchObject then
move(inputObject.Position)
end
end)
userInputService.InputEnded:connect(function(inputObject, isProcessed)
if inputObject == thumbpadTouchObject then
localPlayer:Move(Vector3.new(), true)
thumbpadTouchObject = nil
isUp, isDown, isLeft, isRight = false, false, false, false
doTween(dArrow, arrowSize, UDim2.new(0.5, -arrowSize.X.Offset/2, 1, largeOffset))
doTween(uArrow, arrowSize, UDim2.new(0.5, -arrowSize.X.Offset/2, 0, smallOffset))
doTween(lArrow, arrowSize, UDim2.new(0, smallOffset, 0.5, -arrowSize.Y.Offset/2))
doTween(rArrow, arrowSize, UDim2.new(1, largeOffset, 0.5, -arrowSize.Y.Offset/2))
end
end)
thumbpadFrame.Parent = parentFrame
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
--
-- Touch Control
--
local touchControlFrame = nil
local characterDPad = nil
function setupTouchControls()
touchControlFrame = Instance.new("Frame")
touchControlFrame.Name = "TouchControlFrame"
touchControlFrame.Size = UDim2.new(1,0,1,0)
touchControlFrame.BackgroundTransparency = 1
touchControlFrame.Parent = Game:GetService("CoreGui").RobloxGui
userInputService.ProcessedEvent:connect(function(inputObject, processed)
if not processed then return end
-- kill camera pan if the touch is used by some user controls
if inputObject == cameraTouch and inputObject.UserInputState == Enum.UserInputState.Begin then
setCameraTouch(nil)
end
end)
setupJumpButton(touchControlFrame)
local refreshCharacterMoveFunc = setupCharacterMovement(touchControlFrame)
setupCameraControl(touchControlFrame, refreshCharacterMoveFunc)
characterDPad = createDPad()
characterDPad.Name = "CharacterDPad"
characterDPad.Parent = touchControlFrame
setupDPadControls(characterDPad)
setupThumbpad(touchControlFrame)
userInputService.Changed:connect(function(prop)
if prop == "ModalEnabled" then
activateTouchControls()
end
end)
activateTouchControls()
end
function activateTouchControls()
-- set user controlled visibility
if userInputService.ModalEnabled then
characterThumbstick.Visible = false
characterOuterThumbstick.Visible = false
jumpButton.Visible = false
characterDPad.Visible = false
thumbpadFrame.Visible = false
if currentJumpTouch then
currentJumpTouch = nil
end
else
local modeName = GameSettings.TouchMovementMode.Name
local isThumbpadMode = false
if (modeName == "Thumbstick" or modeName == "Default") then
isInThumbstickMode = true
isThumbpadMode = false
elseif modeName == "Thumbpad" then
isInThumbstickMode = false
isThumbpadMode = true
else
isInThumbstickMode = false
isThumbpadMode = false
end
characterThumbstick.Visible = isInThumbstickMode
characterOuterThumbstick.Visible = isInThumbstickMode
thumbpadFrame.Visible = isThumbpadMode
jumpButton.Visible = isInThumbstickMode or isThumbpadMode
characterDPad.Visible = not isInThumbstickMode and not isThumbpadMode
end
end
----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Start of Script
if userInputService:IsLuaTouchControls() then
setupTouchControls()
else
script:Destroy()
end
GameSettings.Changed:connect(function(property)
if (property == "TouchMovementMode") then
activateTouchControls()
end
end)
end