This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,5 @@
{
"lint": {
"ImportUnused": "fatal"
}
}
@@ -0,0 +1,174 @@
--[[
// FileName: MasterControl
// Version 1.0
// Written by: jeditkacheff
// Description: All character control scripts go thru this script, this script makes sure all actions are performed
--]]
-- [[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local STATE_JUMPING = Enum.HumanoidStateType.Jumping
local STATE_FREEFALL = Enum.HumanoidStateType.Freefall
local STATE_LANDED = Enum.HumanoidStateType.Landed
--[[ Local Variables ]]--
local MasterControl = {}
local Players = game:GetService('Players')
local RunService = game:GetService('RunService')
while not Players.LocalPlayer do
Players.PlayerAdded:wait()
end
local LocalPlayer = Players.LocalPlayer
local LocalCharacter = LocalPlayer.Character
local CachedHumanoid = nil
local isJumping = false
local moveValue = Vector3.new(0, 0, 0)
local isJumpEnabled = true
local areControlsEnabled = true
local clickToMoveFailStateChanged = Instance.new("BindableEvent")
clickToMoveFailStateChanged.Name = "ClickToMoveFailStateChanged"
--[[ Local Functions ]]--
function MasterControl:GetHumanoid()
if LocalCharacter then
if CachedHumanoid then
return CachedHumanoid
else
CachedHumanoid = LocalCharacter:FindFirstChildOfClass("Humanoid")
return CachedHumanoid
end
end
end
local characterAncestryChangedConn = nil
local characterChildRemovedConn = nil
local function characterAdded(character)
if characterAncestryChangedConn then
characterAncestryChangedConn:disconnect()
end
if characterChildRemovedConn then
characterChildRemovedConn:disconnect()
end
LocalCharacter = character
CachedHumanoid = LocalCharacter:FindFirstChildOfClass("Humanoid")
characterAncestryChangedConn = character.AncestryChanged:connect(function()
if character.Parent == nil then
LocalCharacter = nil
else
LocalCharacter = character
end
end)
characterChildRemovedConn = character.ChildRemoved:connect(function(child)
if child == CachedHumanoid then
CachedHumanoid = nil
end
end)
end
if LocalCharacter then
characterAdded(LocalCharacter)
end
LocalPlayer.CharacterAdded:connect(characterAdded)
local getHumanoid = MasterControl.GetHumanoid
local moveFunc = LocalPlayer.Move
local updateMovement = function()
if not areControlsEnabled then return end
local humanoid = getHumanoid()
if not humanoid then return end
if isJumpEnabled and isJumping and not humanoid.PlatformStand then
local state = humanoid:GetState()
if state ~= STATE_JUMPING and state ~= STATE_FREEFALL and state ~= STATE_LANDED then
humanoid.Jump = isJumping
end
end
moveFunc(LocalPlayer, moveValue, true)
end
--[[ Public API ]]--
function MasterControl:Init()
RunService:BindToRenderStep("MasterControlStep", Enum.RenderPriority.Input.Value, updateMovement)
end
function MasterControl:Enable()
areControlsEnabled = true
isJumpEnabled = true
if self.ControlState.Current then
self.ControlState.Current:Enable()
end
end
function MasterControl:Disable()
if self.ControlState.Current then
self.ControlState.Current:Disable()
end
--After current control state is disabled, moveValue has been set to zero,
--Call updateMovement one last time to make sure this propagates to the engine -
--Otherwise if disabled while humanoid is moving, humanoid won't stop moving.
updateMovement()
isJumping = false
areControlsEnabled = false
end
function MasterControl:EnableJump()
isJumpEnabled = true
if areControlsEnabled and self.ControlState:IsTouchJumpModuleUsed() then
self.TouchJumpModule:Enable()
end
end
function MasterControl:DisableJump()
isJumpEnabled = false
if self.ControlState:IsTouchJumpModuleUsed() then
self.TouchJumpModule:Disable()
end
end
function MasterControl:AddToPlayerMovement(playerMoveVector)
moveValue = Vector3.new(moveValue.X + playerMoveVector.X, moveValue.Y + playerMoveVector.Y, moveValue.Z + playerMoveVector.Z)
end
function MasterControl:GetMoveVector()
return moveValue
end
function MasterControl:SetIsJumping(jumping)
if not isJumpEnabled then return end
isJumping = jumping
local humanoid = self:GetHumanoid()
if humanoid and not humanoid.PlatformStand then
humanoid.Jump = isJumping
end
end
function MasterControl:DoJump()
if not isJumpEnabled then return end
local humanoid = self:GetHumanoid()
if humanoid then
humanoid.Jump = true
end
end
function MasterControl:GetClickToMoveFailStateChanged()
return clickToMoveFailStateChanged
end
return MasterControl
@@ -0,0 +1,170 @@
--[[
// FileName: DPad
// Version 1.0
// Written by: jmargh
// Description: Implements DPad controls for touch devices
--]]
local Players = game:GetService('Players')
local GuiService = game:GetService('GuiService')
local DPad = {}
local MasterControl = require(script.Parent)
--[[ Script Variables ]]--
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local DPadFrame = nil
local TouchObject = nil
local OnInputEnded = nil -- defined in Create()
--[[ Constants ]]--
local DPAD_SHEET = "rbxasset://textures/ui/DPadSheet.png"
local COMPASS_DIR = {
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
}
--[[ lua Function Cache ]]--
local ATAN2 = math.atan2
local FLOOR = math.floor
local PI = math.pi
--[[ Local Functions ]]--
local function createArrowLabel(name, position, size, rectOffset, rectSize)
local image = Instance.new('ImageLabel')
image.Name = name
image.Image = DPAD_SHEET
image.ImageRectOffset = rectOffset
image.ImageRectSize = rectSize
image.BackgroundTransparency = 1
image.Size = size
image.Position = position
image.Parent = DPadFrame
return image
end
local function getCenterPosition()
return Vector2.new(DPadFrame.AbsolutePosition.x + DPadFrame.AbsoluteSize.x/2, DPadFrame.AbsolutePosition.y + DPadFrame.AbsoluteSize.y/2)
end
--[[ Public API ]]--
function DPad:Enable()
DPadFrame.Visible = true
end
function DPad:Disable()
DPadFrame.Visible = false
OnInputEnded()
end
function DPad:Create(parentFrame)
if DPadFrame then
DPadFrame:Destroy()
DPadFrame = nil
end
local position = UDim2.new(0, 10, 1, -230)
DPadFrame = Instance.new('Frame')
DPadFrame.Name = "DPadFrame"
DPadFrame.Active = true
DPadFrame.Visible = false
DPadFrame.Size = UDim2.new(0, 192, 0, 192)
DPadFrame.Position = position
DPadFrame.BackgroundTransparency = 1
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", UDim2.new(0.5, -32, 1, -64), lgArrowSize, Vector2.new(0, 0), lgImgOffset)
local fBtn = createArrowLabel("ForwardButton", UDim2.new(0.5, -32, 0, 0), lgArrowSize, Vector2.new(0, 258), lgImgOffset)
local lBtn = createArrowLabel("LeftButton", UDim2.new(0, 0, 0.5, -32), lgArrowSize, Vector2.new(129, 129), lgImgOffset)
local rBtn = createArrowLabel("RightButton", UDim2.new(1, -64, 0.5, -32), lgArrowSize, Vector2.new(0, 129), lgImgOffset)
local jumpBtn = createArrowLabel("JumpButton", UDim2.new(0.5, -32, 0.5, -32), lgArrowSize, Vector2.new(129, 0), lgImgOffset)
local flBtn = createArrowLabel("ForwardLeftButton", UDim2.new(0, 35, 0, 35), smArrowSize, Vector2.new(129, 258), smImgOffset)
local frBtn = createArrowLabel("ForwardRightButton", UDim2.new(1, -55, 0, 35), smArrowSize, Vector2.new(176, 258), smImgOffset)
flBtn.Visible = false
frBtn.Visible = false
-- input connections
jumpBtn.InputBegan:connect(function(inputObject)
MasterControl:DoJump()
end)
local movementVector = Vector3.new(0,0,0)
local function normalizeDirection(inputPosition)
local jumpRadius = jumpBtn.AbsoluteSize.x/2
local centerPosition = getCenterPosition()
local direction = Vector2.new(inputPosition.x - centerPosition.x, inputPosition.y - centerPosition.y)
if direction.magnitude > jumpRadius then
local angle = ATAN2(direction.y, direction.x)
local octant = (FLOOR(8 * angle / (2 * PI) + 8.5)%8) + 1
movementVector = COMPASS_DIR[octant]
end
if not flBtn.Visible and movementVector == COMPASS_DIR[7] then
flBtn.Visible = true
frBtn.Visible = true
end
end
DPadFrame.InputBegan:connect(function(inputObject)
if TouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch then
return
end
MasterControl:AddToPlayerMovement(-movementVector)
TouchObject = inputObject
normalizeDirection(TouchObject.Position)
MasterControl:AddToPlayerMovement(movementVector)
end)
DPadFrame.InputChanged:connect(function(inputObject)
if inputObject == TouchObject then
MasterControl:AddToPlayerMovement(-movementVector)
normalizeDirection(TouchObject.Position)
MasterControl:AddToPlayerMovement(movementVector)
MasterControl:SetIsJumping(false)
end
end)
OnInputEnded = function()
TouchObject = nil
flBtn.Visible = false
frBtn.Visible = false
MasterControl:AddToPlayerMovement(-movementVector)
movementVector = Vector3.new(0, 0, 0)
end
DPadFrame.InputEnded:connect(function(inputObject)
if inputObject == TouchObject then
OnInputEnded()
end
end)
GuiService.MenuOpened:connect(function()
if TouchObject then
OnInputEnded()
end
end)
DPadFrame.Parent = parentFrame
end
return DPad
@@ -0,0 +1,559 @@
--[[
// FileName: DynamicThumbstick
// Version 0.9
// Written by: jhelms
// Description: Implements dynamic thumbstick controls for touch devices
--]]
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
local MasterControl = require(script.Parent)
local Thumbstick = {}
local Enabled = false
--[[ Script Variables ]]--
while not Players.LocalPlayer do
Players.PlayerAdded:wait()
end
local LocalPlayer = Players.LocalPlayer
local Tools = {}
local ToolEquipped = nil
local RevertAutoJumpEnabledToFalse = false
local ThumbstickFrame = nil
local GestureArea = nil
local StartImage = nil
local EndImage = nil
local MiddleImages = {}
local MoveTouchObject = nil
local IsFollowStick = false
local ThumbstickFrame = nil
local OnMoveTouchEnded = nil -- defined in Create()
local OnTouchMovedCn = nil
local OnTouchEndedCn = nil
local TouchActivateCn = nil
local OnRenderSteppedCn = nil
local currentMoveVector = Vector3.new(0,0,0)
local IsFirstTouch = true
--[[ Constants ]]--
local TOUCH_CONTROLS_SHEET = "rbxasset://textures/ui/Input/TouchControlsSheetV2.png"
local MIDDLE_TRANSPARENCIES = {
1 - 0.89,
1 - 0.70,
1 - 0.60,
1 - 0.50,
1 - 0.40,
1 - 0.30,
1 - 0.25
}
local NUM_MIDDLE_IMAGES = #MIDDLE_TRANSPARENCIES
local TOUCH_IS_TAP_TIME_THRESHOLD = 0.5
local TOUCH_IS_TAP_DISTANCE_THRESHOLD = 25
local HasFadedBackgroundInPortrait = false
local HasFadedBackgroundInLandscape = false
local FadeInAndOutBackground = true
local FadeInAndOutMaxAlpha = 0.35
local TweenInAlphaStart = nil
local TweenOutAlphaStart = nil
local FADE_IN_OUT_HALF_DURATION_DEFAULT = 0.3
local FADE_IN_OUT_HALF_DURATION_ORIENTATION_CHANGE = 2
local FADE_IN_OUT_BALANCE_DEFAULT = 0.5
local FadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
local FadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
local ThumbstickFadeTweenInfo = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
--[[ Local functionality ]]--
local function isDynamicThumbstickEnabled()
return ThumbstickFrame and ThumbstickFrame.Visible
end
local function enableAutoJump(humanoid)
if humanoid and isDynamicThumbstickEnabled() then
local shouldRevert = humanoid.AutoJumpEnabled == false
shouldRevert = shouldRevert and LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice
RevertAutoJumpEnabledToFalse = shouldRevert
humanoid.AutoJumpEnabled = true
end
end
do
local function onCharacterAdded(character)
for _, child in ipairs(LocalPlayer.Character:GetChildren()) do
if child:IsA("Tool") then
ToolEquipped = child
end
end
character.ChildAdded:Connect(function(child)
if child:IsA("Tool") then
ToolEquipped = child
elseif child:IsA("Humanoid") then
enableAutoJump(child)
end
end)
character.ChildRemoved:Connect(function(child)
if child:IsA("Tool") then
if child == ToolEquipped then
ToolEquipped = nil
end
end
end)
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
enableAutoJump(humanoid)
end
end
LocalPlayer.CharacterAdded:Connect(onCharacterAdded)
if LocalPlayer.Character then
onCharacterAdded(LocalPlayer.Character)
end
end
--[[ Public API ]]--
function Thumbstick:Enable()
Enabled = true
ThumbstickFrame.Visible = true
local humanoid = MasterControl:GetHumanoid()
enableAutoJump(humanoid)
end
function Thumbstick:Disable()
Enabled = false
if RevertAutoJumpEnabledToFalse then
local humanoid = MasterControl:GetHumanoid()
if humanoid then
humanoid.AutoJumpEnabled = false
end
end
ThumbstickFrame.Visible = false
OnMoveTouchEnded()
end
function Thumbstick:GetInputObject()
return MoveTouchObject
end
function Thumbstick:Create(parentFrame)
if ThumbstickFrame then
ThumbstickFrame:Destroy()
ThumbstickFrame = nil
if OnTouchMovedCn then
OnTouchMovedCn:disconnect()
OnTouchMovedCn = nil
end
if OnTouchEndedCn then
OnTouchEndedCn:disconnect()
OnTouchEndedCn = nil
end
if OnRenderSteppedCn then
OnRenderSteppedCn:disconnect()
OnRenderSteppedCn = nil
end
if TouchActivateCn then
TouchActivateCn:disconnect()
TouchActivateCn = nil
end
end
local ThumbstickSize = 45
local ThumbstickRingSize = 20
local MiddleSize = 10
local MiddleSpacing = MiddleSize + 4
local RadiusOfDeadZone = 2
local RadiusOfMaxSpeed = 50
local screenSize = parentFrame.AbsoluteSize
local isBigScreen = math.min(screenSize.x, screenSize.y) > 500
if isBigScreen then
ThumbstickSize = ThumbstickSize * 2
ThumbstickRingSize = ThumbstickRingSize * 2
MiddleSize = MiddleSize * 2
MiddleSpacing = MiddleSpacing * 2
RadiusOfDeadZone = RadiusOfDeadZone * 2
RadiusOfMaxSpeed = RadiusOfMaxSpeed * 2
end
local color = Color3.fromRGB(255, 255, 255)
local function layoutThumbstickFrame(portraitMode)
if portraitMode then
ThumbstickFrame.Size = UDim2.new(1, 0, 0.4, 0)
ThumbstickFrame.Position = UDim2.new(0, 0, 0.6, 0)
GestureArea.Size = UDim2.new(1, 0, 0.6, 0)
GestureArea.Position = UDim2.new(0, 0, 0, 0)
else
ThumbstickFrame.Size = UDim2.new(0.4, 0, 2/3, 0)
ThumbstickFrame.Position = UDim2.new(0, 0, 1/3, 0)
GestureArea.Size = UDim2.new(1, 0, 1, 0)
GestureArea.Position = UDim2.new(0, 0, 0, 0)
end
end
GestureArea = Instance.new("Frame")
GestureArea.Name = "GestureArea"
GestureArea.Active = false
GestureArea.Visible = true
GestureArea.BackgroundTransparency = 1
GestureArea.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
ThumbstickFrame = Instance.new('Frame')
ThumbstickFrame.Name = "DynamicThumbstickFrame"
ThumbstickFrame.Active = false
ThumbstickFrame.Visible = false
ThumbstickFrame.BackgroundTransparency = 1.0
ThumbstickFrame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
layoutThumbstickFrame()
StartImage = Instance.new("ImageLabel")
StartImage.Name = "ThumbstickStart"
StartImage.Visible = true
StartImage.BackgroundTransparency = 1
StartImage.Image = TOUCH_CONTROLS_SHEET
StartImage.ImageRectOffset = Vector2.new(1,1)
StartImage.ImageRectSize = Vector2.new(144, 144)
StartImage.ImageColor3 = Color3.new(0, 0, 0)
StartImage.AnchorPoint = Vector2.new(0.5, 0.5)
StartImage.Position = UDim2.new(0, ThumbstickRingSize * 3.3, 1, -ThumbstickRingSize * 2.8)
StartImage.Size = UDim2.new(0, ThumbstickRingSize * 3.7, 0, ThumbstickRingSize * 3.7)
StartImage.ZIndex = 10
StartImage.Parent = ThumbstickFrame
EndImage = Instance.new("ImageLabel")
EndImage.Name = "ThumbstickEnd"
EndImage.Visible = true
EndImage.BackgroundTransparency = 1
EndImage.Image = TOUCH_CONTROLS_SHEET
EndImage.ImageRectOffset = Vector2.new(1,1)
EndImage.ImageRectSize = Vector2.new(144, 144)
EndImage.AnchorPoint = Vector2.new(0.5, 0.5)
EndImage.Position = StartImage.Position
EndImage.Size = UDim2.new(0, ThumbstickSize * 0.8, 0, ThumbstickSize * 0.8)
EndImage.ZIndex = 10
EndImage.Parent = ThumbstickFrame
for i = 1, NUM_MIDDLE_IMAGES do
MiddleImages[i] = Instance.new("ImageLabel")
MiddleImages[i].Name = "ThumbstickMiddle"
MiddleImages[i].Visible = false
MiddleImages[i].BackgroundTransparency = 1
MiddleImages[i].Image = TOUCH_CONTROLS_SHEET
MiddleImages[i].ImageRectOffset = Vector2.new(1,1)
MiddleImages[i].ImageRectSize = Vector2.new(144, 144)
MiddleImages[i].ImageTransparency = MIDDLE_TRANSPARENCIES[i]
MiddleImages[i].AnchorPoint = Vector2.new(0.5, 0.5)
MiddleImages[i].ZIndex = 9
MiddleImages[i].Parent = ThumbstickFrame
end
local CameraChangedConn = nil
local function onCurrentCameraChanged()
if CameraChangedConn then
CameraChangedConn:Disconnect()
CameraChangedConn = nil
end
local newCamera = workspace.CurrentCamera
if newCamera then
local function onViewportSizeChanged()
local size = newCamera.ViewportSize
local portraitMode = size.X < size.Y
layoutThumbstickFrame(portraitMode)
end
CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(onViewportSizeChanged)
onViewportSizeChanged()
end
end
workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
if workspace.CurrentCamera then
onCurrentCameraChanged()
end
MoveTouchObject = nil
local MoveTouchStartTime = nil
local MoveTouchStartPosition = nil
local startImageFadeTween, endImageFadeTween, middleImageFadeTweens = nil, nil, {}
local function fadeThumbstick(visible)
if not visible and MoveTouchObject then
return
end
if IsFirstTouch then return end
if startImageFadeTween then
startImageFadeTween:Cancel()
end
if endImageFadeTween then
endImageFadeTween:Cancel()
end
for i = 1, #MiddleImages do
if middleImageFadeTweens[i] then
middleImageFadeTweens[i]:Cancel()
end
end
if visible then
startImageFadeTween = TweenService:Create(StartImage, ThumbstickFadeTweenInfo, { ImageTransparency = 0 })
startImageFadeTween:Play()
endImageFadeTween = TweenService:Create(EndImage, ThumbstickFadeTweenInfo, { ImageTransparency = 0.2 })
endImageFadeTween:Play()
for i = 1, #MiddleImages do
middleImageFadeTweens[i] = TweenService:Create(MiddleImages[i], ThumbstickFadeTweenInfo, { ImageTransparency = MIDDLE_TRANSPARENCIES[i] })
middleImageFadeTweens[i]:Play()
end
else
startImageFadeTween = TweenService:Create(StartImage, ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
startImageFadeTween:Play()
endImageFadeTween = TweenService:Create(EndImage, ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
endImageFadeTween:Play()
for i = 1, #MiddleImages do
middleImageFadeTweens[i] = TweenService:Create(MiddleImages[i], ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
middleImageFadeTweens[i]:Play()
end
end
end
local function fadeThumbstickFrame(fadeDuration, fadeRatio)
FadeInAndOutHalfDuration = fadeDuration * 0.5
FadeInAndOutBalance = fadeRatio
TweenInAlphaStart = tick()
end
local function doMove(direction)
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = direction
-- Scaled Radial Dead Zone
local inputAxisMagnitude = currentMoveVector.magnitude
if inputAxisMagnitude < RadiusOfDeadZone then
currentMoveVector = Vector3.new()
else
currentMoveVector = currentMoveVector.unit*(1 - math.max(0, (RadiusOfMaxSpeed - currentMoveVector.magnitude)/RadiusOfMaxSpeed))
currentMoveVector = Vector3.new(currentMoveVector.x, 0, currentMoveVector.y)
end
MasterControl:AddToPlayerMovement(currentMoveVector)
end
local function layoutMiddleImages(startPos, endPos)
local startDist = (ThumbstickSize / 2) + MiddleSize
local vector = endPos - startPos
local distAvailable = vector.magnitude - (ThumbstickRingSize / 2) - MiddleSize
local direction = vector.unit
local distNeeded = MiddleSpacing * NUM_MIDDLE_IMAGES
local spacing = MiddleSpacing
if distNeeded < distAvailable then
spacing = distAvailable / NUM_MIDDLE_IMAGES
end
for i = 1, NUM_MIDDLE_IMAGES do
local image = MiddleImages[i]
local distWithout = startDist + (spacing * (i - 2))
local currentDist = startDist + (spacing * (i - 1))
if distWithout < distAvailable then
local pos = endPos - direction * currentDist
local exposedFraction = math.clamp(1 - ((currentDist - distAvailable) / spacing), 0, 1)
image.Visible = true
image.Position = UDim2.new(0, pos.X, 0, pos.Y)
image.Size = UDim2.new(0, MiddleSize * exposedFraction, 0, MiddleSize * exposedFraction)
else
image.Visible = false
end
end
end
local function moveStick(pos)
local startPos = Vector2.new(MoveTouchStartPosition.X, MoveTouchStartPosition.Y) - ThumbstickFrame.AbsolutePosition
local endPos = Vector2.new(pos.X, pos.Y) - ThumbstickFrame.AbsolutePosition
local relativePosition = endPos - startPos
local length = relativePosition.magnitude
local maxLength = ThumbstickFrame.AbsoluteSize.X
length = math.min(length, maxLength)
relativePosition = relativePosition*length
EndImage.Position = UDim2.new(0, endPos.X, 0, endPos.Y)
layoutMiddleImages(startPos, endPos)
end
-- input connections
ThumbstickFrame.InputBegan:connect(function(inputObject)
if inputObject.UserInputType ~= Enum.UserInputType.Touch or inputObject.UserInputState ~= Enum.UserInputState.Begin then
return
end
if MoveTouchObject then
return
end
if IsFirstTouch then
IsFirstTouch = false
local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out,0,false,0)
TweenService:Create(StartImage, tweenInfo, {Size = UDim2.new(0, 0, 0, 0)}):Play()
TweenService:Create(EndImage, tweenInfo, {Size = UDim2.new(0, ThumbstickSize, 0, ThumbstickSize), ImageColor3 = Color3.new(0,0,0)}):Play()
end
MoveTouchObject = inputObject
MoveTouchStartTime = tick()
MoveTouchStartPosition = inputObject.Position
local startPosVec2 = Vector2.new(inputObject.Position.X - ThumbstickFrame.AbsolutePosition.X, inputObject.Position.Y - ThumbstickFrame.AbsolutePosition.Y)
StartImage.Visible = true
StartImage.Position = UDim2.new(0, startPosVec2.X, 0, startPosVec2.Y)
EndImage.Visible = true
EndImage.Position = StartImage.Position
fadeThumbstick(true)
moveStick(inputObject.Position)
if FadeInAndOutBackground then
local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
local hasFadedBackgroundInOrientation = false
-- only fade in/out the background once per orientation
if playerGui then
if playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or
playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight then
hasFadedBackgroundInOrientation = HasFadedBackgroundInLandscape
HasFadedBackgroundInLandscape = true
elseif playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait then
hasFadedBackgroundInOrientation = HasFadedBackgroundInPortrait
HasFadedBackgroundInPortrait = true
end
end
if not hasFadedBackgroundInOrientation then
FadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
FadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
TweenInAlphaStart = tick()
end
end
end)
OnTouchMovedCn = UserInputService.TouchMoved:connect(function(inputObject, isProcessed)
if inputObject == MoveTouchObject then
local direction = Vector2.new(inputObject.Position.x - MoveTouchStartPosition.x, inputObject.Position.y - MoveTouchStartPosition.y)
if math.abs(direction.x) > 0 or math.abs(direction.y) > 0 then
doMove(direction)
moveStick(inputObject.Position)
end
end
end)
OnMoveTouchEnded = function(inputObject)
if inputObject then
local direction = Vector2.new(inputObject.Position.x - MoveTouchStartPosition.x, inputObject.Position.y - MoveTouchStartPosition.y)
end
MoveTouchObject = nil
fadeThumbstick(false)
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
end
OnRenderSteppedCn = RunService.RenderStepped:Connect(function(step)
if TweenInAlphaStart ~= nil then
local delta = tick() - TweenInAlphaStart
local fadeInTime = (FadeInAndOutHalfDuration * 2 * FadeInAndOutBalance)
ThumbstickFrame.BackgroundTransparency = 1 - FadeInAndOutMaxAlpha*math.min(delta/fadeInTime, 1)
if delta > fadeInTime then
TweenOutAlphaStart = tick()
TweenInAlphaStart = nil
end
elseif TweenOutAlphaStart ~= nil then
local delta = tick() - TweenOutAlphaStart
local fadeOutTime = (FadeInAndOutHalfDuration * 2) - (FadeInAndOutHalfDuration * 2 * FadeInAndOutBalance)
ThumbstickFrame.BackgroundTransparency = 1 - FadeInAndOutMaxAlpha + FadeInAndOutMaxAlpha*math.min(delta/fadeOutTime, 1)
if delta > fadeOutTime then
TweenOutAlphaStart = nil
end
end
end)
OnTouchEndedCn = UserInputService.TouchEnded:connect(function(inputObject, isProcessed)
if inputObject == MoveTouchObject then
OnMoveTouchEnded(inputObject)
end
end)
GuiService.MenuOpened:connect(function()
if MoveTouchObject then
OnMoveTouchEnded(nil)
end
end)
local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
while not playerGui do
LocalPlayer.ChildAdded:wait()
playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
end
local playerGuiChangedConn = nil
local originalScreenOrientationWasLandscape = playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or
playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight
local function longShowBackground()
FadeInAndOutHalfDuration = 2.5
FadeInAndOutBalance = 0.05
TweenInAlphaStart = tick()
end
playerGuiChangedConn = playerGui.Changed:connect(function(prop)
if prop == "CurrentScreenOrientation" then
if (originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait) or
(not originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation ~= Enum.ScreenOrientation.Portrait) then
playerGuiChangedConn:disconnect()
longShowBackground()
if originalScreenOrientationWasLandscape then
HasFadedBackgroundInPortrait = true
else
HasFadedBackgroundInLandscape = true
end
end
end
end)
GestureArea.Parent = parentFrame.Parent
ThumbstickFrame.Parent = parentFrame
spawn(function()
if game:IsLoaded() then
longShowBackground()
else
game.Loaded:wait()
longShowBackground()
end
end)
end
return Thumbstick
@@ -0,0 +1,198 @@
--[[
// FileName: Gamepad
// Written by: jeditkacheff
// Description: Implements movement controls for gamepad devices (XBox, PS4, MFi, etc.)
--]]
local Gamepad = {}
local MasterControl = require(script.Parent)
local Players = game:GetService('Players')
local RunService = game:GetService('RunService')
local UserInputService = game:GetService('UserInputService')
local ContextActionService = game:GetService('ContextActionService')
local StarterPlayer = game:GetService('StarterPlayer')
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
local currentMoveVector = Vector3.new(0,0,0)
local activateGamepad = nil
local gamepadConnectedCon = nil
local gamepadDisconnectedCon = nil
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
--[[ Constants ]]--
local thumbstickDeadzone = 0.22 --raised from 14% on 3/1/16 to accommodate looser XB360 controllers
function assignActivateGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
if #connectedGamepads > 0 then
for i = 1, #connectedGamepads do
if activateGamepad == nil then
activateGamepad = connectedGamepads[i]
elseif connectedGamepads[i].Value < activateGamepad.Value then
activateGamepad = connectedGamepads[i]
end
end
end
if activateGamepad == nil then -- nothing is connected, at least set up for gamepad1
activateGamepad = Enum.UserInputType.Gamepad1
end
end
--[[ Public API ]]--
function Gamepad:Enable()
local forwardValue = 0
local backwardValue = 0
local leftValue = 0
local rightValue = 0
local moveFunc = LocalPlayer.Move
local gamepadSupports = UserInputService.GamepadSupports
local controlCharacterGamepad = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Cancel then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
return
end
if activateGamepad ~= inputObject.UserInputType then return end
if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end
if inputObject.Position.magnitude > thumbstickDeadzone then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(inputObject.Position.X, 0, -inputObject.Position.Y)
MasterControl:AddToPlayerMovement(currentMoveVector)
else
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
end
end
local jumpCharacterGamepad = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Cancel then
MasterControl:SetIsJumping(false)
return
end
if activateGamepad ~= inputObject.UserInputType then return end
if inputObject.KeyCode ~= Enum.KeyCode.ButtonA then return end
MasterControl:SetIsJumping(inputObject.UserInputState == Enum.UserInputState.Begin)
end
local doDpadMoveUpdate = function(userInputType)
if LocalPlayer and LocalPlayer.Character then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(leftValue + rightValue,0,forwardValue + backwardValue)
MasterControl:AddToPlayerMovement(currentMoveVector)
end
end
local moveForwardFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
forwardValue = -1
elseif inputState == Enum.UserInputState.Begin or inputState == Enum.UserInputState.Cancel then
forwardValue = 0
end
doDpadMoveUpdate(inputObject.UserInputType)
end
local moveBackwardFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
backwardValue = 1
elseif inputState == Enum.UserInputState.Begin or inputState == Enum.UserInputState.Cancel then
backwardValue = 0
end
doDpadMoveUpdate(inputObject.UserInputType)
end
local moveLeftFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
leftValue = -1
elseif inputState == Enum.UserInputState.Begin or inputState == Enum.UserInputState.Cancel then
leftValue = 0
end
doDpadMoveUpdate(inputObject.UserInputType)
end
local moveRightFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
rightValue = 1
elseif inputState == Enum.UserInputState.Begin or inputState == Enum.UserInputState.Cancel then
rightValue = 0
end
doDpadMoveUpdate(inputObject.UserInputType)
end
local function setActivateGamepad()
if activateGamepad then
ContextActionService:UnbindActivate(activateGamepad, Enum.KeyCode.ButtonR2)
end
assignActivateGamepad()
if activateGamepad then
ContextActionService:BindActivate(activateGamepad, Enum.KeyCode.ButtonR2)
end
end
ContextActionService:BindAction("JumpButton",jumpCharacterGamepad, false, Enum.KeyCode.ButtonA)
ContextActionService:BindAction("MoveThumbstick",controlCharacterGamepad, false, Enum.KeyCode.Thumbstick1)
setActivateGamepad()
if not gamepadSupports(UserInputService, activateGamepad, Enum.KeyCode.Thumbstick1) then
-- if the gamepad supports thumbsticks, theres no point in having the dpad buttons getting eaten up by these actions
ContextActionService:BindAction("forwardDpad", moveForwardFunc, false, Enum.KeyCode.DPadUp)
ContextActionService:BindAction("backwardDpad", moveBackwardFunc, false, Enum.KeyCode.DPadDown)
ContextActionService:BindAction("leftDpad", moveLeftFunc, false, Enum.KeyCode.DPadLeft)
ContextActionService:BindAction("rightDpad", moveRightFunc, false, Enum.KeyCode.DPadRight)
end
gamepadConnectedCon = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if activateGamepad ~= gamepadEnum then return end
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
activateGamepad = nil
setActivateGamepad()
end)
gamepadDisconnectedCon = UserInputService.GamepadConnected:connect(function(gamepadEnum)
if activateGamepad == nil then
setActivateGamepad()
end
end)
end
function Gamepad:Disable()
ContextActionService:UnbindAction("forwardDpad")
ContextActionService:UnbindAction("backwardDpad")
ContextActionService:UnbindAction("leftDpad")
ContextActionService:UnbindAction("rightDpad")
ContextActionService:UnbindAction("MoveThumbstick")
ContextActionService:UnbindAction("JumpButton")
ContextActionService:UnbindActivate(activateGamepad, Enum.KeyCode.ButtonR2)
if gamepadConnectedCon then gamepadConnectedCon:disconnect() end
if gamepadDisconnectedCon then gamepadDisconnectedCon:disconnect() end
activateGamepad = nil
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
MasterControl:SetIsJumping(false)
end
return Gamepad
@@ -0,0 +1,175 @@
--[[
// FileName: ComputerMovementKeyboardMovement
// Version 1.2
// Written by: jeditkacheff/jmargh
// Description: Implements movement controls for keyboard devices
--]]
local Players = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local ContextActionService = game:GetService('ContextActionService')
local StarterPlayer = game:GetService('StarterPlayer')
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
local KeyboardMovement = {}
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local CachedHumanoid = nil
local SeatJumpCn = nil
local TextFocusReleasedCn = nil
local TextFocusGainedCn = nil
local WindowFocusReleasedCn = nil
local MasterControl = require(script.Parent)
local currentMoveVector = Vector3.new(0,0,0)
--[[ Local Functions ]]--
local function getHumanoid()
local character = LocalPlayer and LocalPlayer.Character
if character then
if CachedHumanoid and CachedHumanoid.Parent == character then
return CachedHumanoid
else
CachedHumanoid = nil
for _,child in pairs(character:GetChildren()) do
if child:IsA('Humanoid') then
CachedHumanoid = child
return CachedHumanoid
end
end
end
end
end
--[[ Public API ]]--
function KeyboardMovement:Enable()
if not UserInputService.KeyboardEnabled then
return
end
local forwardValue = 0
local backwardValue = 0
local leftValue = 0
local rightValue = 0
local updateMovement = function(inputState)
if inputState == Enum.UserInputState.Cancel then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0, 0, 0)
else
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(leftValue + rightValue,0,forwardValue + backwardValue)
MasterControl:AddToPlayerMovement(currentMoveVector)
end
end
local moveForwardFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
forwardValue = -1
elseif inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
forwardValue = 0
end
updateMovement(inputState)
end
local moveBackwardFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
backwardValue = 1
elseif inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
backwardValue = 0
end
updateMovement(inputState)
end
local moveLeftFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
leftValue = -1
elseif inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
leftValue = 0
end
updateMovement(inputState)
end
local moveRightFunc = function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
rightValue = 1
elseif inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
rightValue = 0
end
updateMovement(inputState)
end
local jumpFunc = function(actionName, inputState, inputObject)
MasterControl:SetIsJumping(inputState == Enum.UserInputState.Begin)
end
-- TODO: remove up and down arrows, these seem unnecessary
ContextActionService:BindActionToInputTypes("forwardMovement", moveForwardFunc, false, Enum.PlayerActions.CharacterForward)
ContextActionService:BindActionToInputTypes("backwardMovement", moveBackwardFunc, false, Enum.PlayerActions.CharacterBackward)
ContextActionService:BindActionToInputTypes("leftMovement", moveLeftFunc, false, Enum.PlayerActions.CharacterLeft)
ContextActionService:BindActionToInputTypes("rightMovement", moveRightFunc, false, Enum.PlayerActions.CharacterRight)
ContextActionService:BindActionToInputTypes("jumpAction", jumpFunc, false, Enum.PlayerActions.CharacterJump)
-- TODO: make sure we check key state before binding to check if key is already down
local function onFocusReleased()
local humanoid = getHumanoid()
if humanoid then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0, 0, 0)
forwardValue, backwardValue, leftValue, rightValue = 0, 0, 0, 0
MasterControl:SetIsJumping(false)
end
end
local function onTextFocusGained(textboxFocused)
MasterControl:SetIsJumping(false)
end
SeatJumpCn = UserInputService.InputBegan:connect(function(inputObject, isProcessed)
if inputObject.KeyCode == Enum.KeyCode.Backspace and not isProcessed then
local humanoid = getHumanoid()
if humanoid and (humanoid.Sit or humanoid.PlatformStand) then
MasterControl:DoJump()
end
end
end)
TextFocusReleasedCn = UserInputService.TextBoxFocusReleased:connect(onFocusReleased)
TextFocusGainedCn = UserInputService.TextBoxFocused:connect(onTextFocusGained)
-- TODO: remove pcall when API is live
WindowFocusReleasedCn = UserInputService.WindowFocusReleased:connect(onFocusReleased)
end
function KeyboardMovement:Disable()
ContextActionService:UnbindAction("forwardMovement")
ContextActionService:UnbindAction("backwardMovement")
ContextActionService:UnbindAction("leftMovement")
ContextActionService:UnbindAction("rightMovement")
ContextActionService:UnbindAction("jumpAction")
if SeatJumpCn then
SeatJumpCn:disconnect()
SeatJumpCn = nil
end
if TextFocusReleasedCn then
TextFocusReleasedCn:disconnect()
TextFocusReleasedCn = nil
end
if TextFocusGainedCn then
TextFocusGainedCn:disconnect()
TextFocusGainedCn = nil
end
if WindowFocusReleasedCn then
WindowFocusReleasedCn:disconnect()
WindowFocusReleasedCn = nil
end
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
MasterControl:SetIsJumping(false)
end
return KeyboardMovement
@@ -0,0 +1,228 @@
--[[
// FileName: Thumbpad
// Version 1.0
// Written by: jmargh
// Description: Implements thumbpad controls for touch devices
--]]
local Players = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local GuiService = game:GetService('GuiService')
local Thumbpad = {}
local MasterControl = require(script.Parent)
--[[ Script Variables ]]--
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local ThumbpadFrame = nil
local TouchObject = nil
local OnInputEnded = nil -- is defined in Create()
local OnTouchChangedCn = nil
local OnTouchEndedCn = nil
local currentMoveVector = Vector3.new(0,0,0)
--[[ Constants ]]--
local DPAD_SHEET = "rbxasset://textures/ui/DPadSheet.png"
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/TouchControlsSheet.png"
--[[ Local Functions ]]--
local function createArrowLabel(name, parent, position, size, rectOffset, rectSize)
local image = Instance.new('ImageLabel')
image.Name = name
image.Image = DPAD_SHEET
image.ImageRectOffset = rectOffset
image.ImageRectSize = rectSize
image.BackgroundTransparency = 1
image.ImageColor3 = Color3.new(190/255, 190/255, 190/255)
image.Size = size
image.Position = position
image.Parent = parent
return image
end
--[[ Public API ]]--
function Thumbpad:Enable()
ThumbpadFrame.Visible = true
end
function Thumbpad:Disable()
ThumbpadFrame.Visible = false
OnInputEnded()
end
function Thumbpad:Create(parentFrame)
if ThumbpadFrame then
ThumbpadFrame:Destroy()
ThumbpadFrame = nil
if OnTouchChangedCn then
OnTouchChangedCn:disconnect()
OnTouchChangedCn = nil
end
if OnTouchEndedCn then
OnTouchEndedCn:disconnect()
OnTouchEndedCn = nil
end
end
local minAxis = math.min(parentFrame.AbsoluteSize.x, parentFrame.AbsoluteSize.y)
local isSmallScreen = minAxis <= 500
local thumbpadSize = isSmallScreen and 70 or 120
local position = isSmallScreen and UDim2.new(0, thumbpadSize * 1.25, 1, -thumbpadSize - 20) or
UDim2.new(0, thumbpadSize/2 - 10, 1, -thumbpadSize * 1.75 - 10)
ThumbpadFrame = Instance.new('Frame')
ThumbpadFrame.Name = "ThumbpadFrame"
ThumbpadFrame.Visible = false
ThumbpadFrame.Active = true
ThumbpadFrame.Size = UDim2.new(0, thumbpadSize + 20, 0, thumbpadSize + 20)
ThumbpadFrame.Position = position
ThumbpadFrame.BackgroundTransparency = 1
local outerImage = Instance.new('ImageLabel')
outerImage.Name = "OuterImage"
outerImage.Image = TOUCH_CONTROL_SHEET
outerImage.ImageRectOffset = Vector2.new(0, 0)
outerImage.ImageRectSize = Vector2.new(220, 220)
outerImage.BackgroundTransparency = 1
outerImage.Size = UDim2.new(0, thumbpadSize, 0, thumbpadSize)
outerImage.Position = UDim2.new(0, 10, 0, 10)
outerImage.Parent = ThumbpadFrame
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(110, 110)
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(8, 8), imgRectSize)
local uArrow = createArrowLabel("UpArrow", outerImage, UDim2.new(0.5, -smArrowSize.X.Offset/2, 0, smImgOffset), smArrowSize, Vector2.new(8, 266), imgRectSize)
local lArrow = createArrowLabel("LeftArrow", outerImage, UDim2.new(0, smImgOffset, 0.5, -smArrowSize.Y.Offset/2), smArrowSize, Vector2.new(137, 137), imgRectSize)
local rArrow = createArrowLabel("RightArrow", outerImage, UDim2.new(1, lgImgOffset, 0.5, -smArrowSize.Y.Offset/2), smArrowSize, Vector2.new(8, 137), 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.1
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)
MasterControl:AddToPlayerMovement(-currentMoveVector)
local delta = Vector2.new(pos.x, pos.y) - padOrigin
currentMoveVector = delta / (thumbpadSize/2)
-- Scaled Radial Dead Zone
local inputAxisMagnitude = currentMoveVector.magnitude
if inputAxisMagnitude < deadZone then
currentMoveVector = Vector3.new(0, 0, 0)
else
currentMoveVector = currentMoveVector.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
-- catch possible NAN Vector
if currentMoveVector.magnitude == 0 then
currentMoveVector = Vector3.new(0, 0, 0)
else
currentMoveVector = Vector3.new(currentMoveVector.x, 0, currentMoveVector.y).unit
end
end
MasterControl:AddToPlayerMovement(currentMoveVector)
local forwardDot = currentMoveVector:Dot(vForward)
local rightDot = currentMoveVector: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
ThumbpadFrame.InputBegan:connect(function(inputObject)
--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
if TouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
or inputObject.UserInputState ~= Enum.UserInputState.Begin then
return
end
ThumbpadFrame.Position = UDim2.new(0, inputObject.Position.x - ThumbpadFrame.AbsoluteSize.x/2, 0, inputObject.Position.y - ThumbpadFrame.Size.Y.Offset/2)
padOrigin = Vector2.new(ThumbpadFrame.AbsolutePosition.x + ThumbpadFrame.AbsoluteSize.x/2,
ThumbpadFrame.AbsolutePosition.y + ThumbpadFrame.AbsoluteSize.y/2)
doMove(inputObject.Position)
TouchObject = inputObject
end)
OnTouchChangedCn = UserInputService.TouchMoved:connect(function(inputObject, isProcessed)
if inputObject == TouchObject then
doMove(TouchObject.Position)
end
end)
OnInputEnded = function()
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
MasterControl:SetIsJumping(false)
ThumbpadFrame.Position = position
TouchObject = 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
OnTouchEndedCn = UserInputService.TouchEnded:connect(function(inputObject)
if inputObject == TouchObject then
OnInputEnded()
end
end)
GuiService.MenuOpened:connect(function()
if TouchObject then
OnInputEnded()
end
end)
ThumbpadFrame.Parent = parentFrame
end
return Thumbpad
@@ -0,0 +1,178 @@
--[[
// FileName: Thumbstick
// Version 1.0
// Written by: jmargh
// Description: Implements thumbstick controls for touch devices
--]]
local Players = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local GuiService = game:GetService('GuiService')
local MasterControl = require(script.Parent)
local Thumbstick = {}
--[[ Script Variables ]]--
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local IsFollowStick = false
local ThumbstickFrame = nil
local MoveTouchObject = nil
local OnTouchEnded = nil -- defined in Create()
local OnTouchMovedCn = nil
local OnTouchEndedCn = nil
local currentMoveVector = Vector3.new(0,0,0)
--[[ Constants ]]--
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/TouchControlsSheet.png"
--[[ Public API ]]--
function Thumbstick:Enable()
ThumbstickFrame.Visible = true
end
function Thumbstick:Disable()
ThumbstickFrame.Visible = false
OnTouchEnded()
end
function Thumbstick:Create(parentFrame)
if ThumbstickFrame then
ThumbstickFrame:Destroy()
ThumbstickFrame = nil
if OnTouchMovedCn then
OnTouchMovedCn:disconnect()
OnTouchMovedCn = nil
end
if OnTouchEndedCn then
OnTouchEndedCn:disconnect()
OnTouchEndedCn = nil
end
end
local minAxis = math.min(parentFrame.AbsoluteSize.x, parentFrame.AbsoluteSize.y)
local isSmallScreen = minAxis <= 500
local thumbstickSize = isSmallScreen and 70 or 120
local position = isSmallScreen and UDim2.new(0, (thumbstickSize/2) - 10, 1, -thumbstickSize - 20) or
UDim2.new(0, thumbstickSize/2, 1, -thumbstickSize * 1.75)
ThumbstickFrame = Instance.new('Frame')
ThumbstickFrame.Name = "ThumbstickFrame"
ThumbstickFrame.Active = true
ThumbstickFrame.Visible = false
ThumbstickFrame.Size = UDim2.new(0, thumbstickSize, 0, thumbstickSize)
ThumbstickFrame.Position = position
ThumbstickFrame.BackgroundTransparency = 1
local outerImage = Instance.new('ImageLabel')
outerImage.Name = "OuterImage"
outerImage.Image = TOUCH_CONTROL_SHEET
outerImage.ImageRectOffset = Vector2.new()
outerImage.ImageRectSize = Vector2.new(220, 220)
outerImage.BackgroundTransparency = 1
outerImage.Size = UDim2.new(0, thumbstickSize, 0, thumbstickSize)
outerImage.Position = UDim2.new(0, 0, 0, 0)
outerImage.Parent = ThumbstickFrame
local StickImage = Instance.new('ImageLabel')
StickImage.Name = "StickImage"
StickImage.Image = TOUCH_CONTROL_SHEET
StickImage.ImageRectOffset = Vector2.new(220, 0)
StickImage.ImageRectSize = Vector2.new(111, 111)
StickImage.BackgroundTransparency = 1
StickImage.Size = UDim2.new(0, thumbstickSize/2, 0, thumbstickSize/2)
StickImage.Position = UDim2.new(0, thumbstickSize/2 - thumbstickSize/4, 0, thumbstickSize/2 - thumbstickSize/4)
StickImage.ZIndex = 2
StickImage.Parent = ThumbstickFrame
local centerPosition = nil
local deadZone = 0.05
local function doMove(direction)
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = direction / (thumbstickSize/2)
-- Scaled Radial Dead Zone
local inputAxisMagnitude = currentMoveVector.magnitude
if inputAxisMagnitude < deadZone then
currentMoveVector = Vector3.new()
else
currentMoveVector = currentMoveVector.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
-- NOTE: Making currentMoveVector a unit vector will cause the player to instantly go max speed
-- must check for zero length vector is using unit
currentMoveVector = Vector3.new(currentMoveVector.x, 0, currentMoveVector.y)
end
MasterControl:AddToPlayerMovement(currentMoveVector)
end
local function moveStick(pos)
local relativePosition = Vector2.new(pos.x - centerPosition.x, pos.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, pos.x - ThumbstickFrame.AbsoluteSize.x/2 - offset.x,
0, pos.y - ThumbstickFrame.AbsoluteSize.y/2 - offset.y)
else
length = math.min(length, maxLength)
relativePosition = relativePosition.unit * length
end
StickImage.Position = UDim2.new(0, relativePosition.x + StickImage.AbsoluteSize.x/2, 0, relativePosition.y + StickImage.AbsoluteSize.y/2)
end
-- input connections
ThumbstickFrame.InputBegan:connect(function(inputObject)
--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
if MoveTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
or inputObject.UserInputState ~= Enum.UserInputState.Begin 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)
end)
OnTouchMovedCn = 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)
OnTouchEnded = function()
ThumbstickFrame.Position = position
StickImage.Position = UDim2.new(0, ThumbstickFrame.Size.X.Offset/2 - thumbstickSize/4, 0, ThumbstickFrame.Size.Y.Offset/2 - thumbstickSize/4)
MoveTouchObject = nil
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
MasterControl:SetIsJumping(false)
end
OnTouchEndedCn = UserInputService.TouchEnded:connect(function(inputObject, isProcessed)
if inputObject == MoveTouchObject then
OnTouchEnded()
end
end)
GuiService.MenuOpened:connect(function()
if MoveTouchObject then
OnTouchEnded()
end
end)
ThumbstickFrame.Parent = parentFrame
end
return Thumbstick
@@ -0,0 +1,172 @@
--[[
// FileName: TouchJump
// Version 1.0
// Written by: jmargh
// Description: Implements jump controls for touch devices. Use with Thumbstick and Thumbpad
--]]
local Players = game:GetService('Players')
local GuiService = game:GetService('GuiService')
local TouchJump = {}
local MasterControl = require(script.Parent)
--[[ Script Variables ]]--
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local Humanoid = MasterControl:GetHumanoid()
local JumpButton = nil
local OnInputEnded = nil -- defined in Create()
local CharacterAddedConnection = nil
local HumStateConnection = nil
local HumChangeConnection = nil
local ExternallyEnabled = false
local JumpPower = 0
local JumpStateEnabled = true
--[[ Constants ]]--
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/Input/TouchControlsSheetV2.png"
--[[ Private Functions ]]--
local function disableButton()
JumpButton.Visible = false
OnInputEnded()
end
local function enableButton()
if Humanoid and ExternallyEnabled then
if ExternallyEnabled then
if Humanoid.JumpPower > 0 then
JumpButton.Visible = true
end
end
end
end
local function updateEnabled()
if JumpPower > 0 and JumpStateEnabled then
enableButton()
else
disableButton()
end
end
local function humanoidChanged(prop)
if prop == "JumpPower" then
JumpPower = Humanoid.JumpPower
updateEnabled()
elseif prop == "Parent" then
if not Humanoid.Parent then
HumChangeConnection:disconnect()
end
end
end
local function humandoidStateEnabledChanged(state, isEnabled)
if state == Enum.HumanoidStateType.Jumping then
JumpStateEnabled = isEnabled
updateEnabled()
end
end
local function characterAdded(newCharacter)
if HumChangeConnection then
HumChangeConnection:disconnect()
end
-- rebind event to new Humanoid
Humanoid = nil
repeat
Humanoid = MasterControl:GetHumanoid()
wait()
until Humanoid and Humanoid.Parent == newCharacter
HumChangeConnection = Humanoid.Changed:connect(humanoidChanged)
HumStateConnection = Humanoid.StateEnabledChanged:connect(humandoidStateEnabledChanged)
JumpPower = Humanoid.JumpPower
JumpStateEnabled = Humanoid:GetStateEnabled(Enum.HumanoidStateType.Jumping)
updateEnabled()
end
local function setupCharacterAddedFunction()
CharacterAddedConnection = LocalPlayer.CharacterAdded:connect(characterAdded)
if LocalPlayer.Character then
characterAdded(LocalPlayer.Character)
end
end
--[[ Public API ]]--
function TouchJump:Enable()
ExternallyEnabled = true
enableButton()
end
function TouchJump:Disable()
ExternallyEnabled = false
disableButton()
end
function TouchJump:Create(parentFrame)
if JumpButton then
JumpButton:Destroy()
JumpButton = nil
end
local minAxis = math.min(parentFrame.AbsoluteSize.x, parentFrame.AbsoluteSize.y)
local isSmallScreen = minAxis <= 500
local jumpButtonSize = isSmallScreen and 70 or 120
JumpButton = Instance.new('ImageButton')
JumpButton.Name = "JumpButton"
JumpButton.Visible = false
JumpButton.BackgroundTransparency = 1
JumpButton.Image = TOUCH_CONTROL_SHEET
JumpButton.ImageRectOffset = Vector2.new(1, 146)
JumpButton.ImageRectSize = Vector2.new(144, 144)
JumpButton.Size = UDim2.new(0, jumpButtonSize, 0, jumpButtonSize)
JumpButton.Position = isSmallScreen and UDim2.new(1, -(jumpButtonSize*1.5-10), 1, -jumpButtonSize - 20) or
UDim2.new(1, -(jumpButtonSize*1.5-10), 1, -jumpButtonSize * 1.75)
local touchObject = nil
JumpButton.InputBegan:connect(function(inputObject)
--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
if touchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
or inputObject.UserInputState ~= Enum.UserInputState.Begin then
return
end
touchObject = inputObject
JumpButton.ImageRectOffset = Vector2.new(146, 146)
MasterControl:SetIsJumping(true)
end)
OnInputEnded = function()
touchObject = nil
MasterControl:SetIsJumping(false)
JumpButton.ImageRectOffset = Vector2.new(1, 146)
end
JumpButton.InputEnded:connect(function(inputObject)
if inputObject == touchObject then
OnInputEnded()
end
end)
GuiService.MenuOpened:connect(function()
if touchObject then
OnInputEnded()
end
end)
if not CharacterAddedConnection then
setupCharacterAddedFunction()
end
JumpButton.Parent = parentFrame
end
return TouchJump
@@ -0,0 +1,433 @@
local VRService = game:GetService("VRService")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local ContextActionService = game:GetService("ContextActionService")
local StarterGui = game:GetService("StarterGui")
local MasterControl = require(script.Parent)
local PathDisplay = nil
local LocalPlayer = Players.LocalPlayer
local VRNavigation = {}
local RECALCULATE_PATH_THRESHOLD = 4
local NO_PATH_THRESHOLD = 12
local MAX_PATHING_DISTANCE = 200
local POINT_REACHED_THRESHOLD = 1
local STOPPING_DISTANCE = 4
local OFFTRACK_TIME_THRESHOLD = 2
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local XZ_VECTOR3 = Vector3.new(1, 0, 1)
local THUMBSTICK_DEADZONE = 0.22
local navigationRequestedConn = nil
local heartbeatConn = nil
local currentDestination = nil
local currentPath = nil
local currentPoints = nil
local currentPointIdx = 0
local currentMoveVector = Vector3.new(0, 0, 0)
local expectedTimeToNextPoint = 0
local timeReachedLastPoint = tick()
local movementUpdateEvent = Instance.new("BindableEvent")
movementUpdateEvent.Name = "MovementUpdate"
movementUpdateEvent.Parent = script
coroutine.wrap(function()
local PathDisplayModule = script.Parent.Parent:WaitForChild("PathDisplay")
if PathDisplayModule then
PathDisplay = require(PathDisplayModule)
end
end)()
local function setLaserPointerMode(mode)
pcall(function()
StarterGui:SetCore("VRLaserPointerMode", mode)
end)
end
local function getLocalHumanoid()
local character = LocalPlayer.Character
if not character then
return
end
for _, child in pairs(character:GetChildren()) do
if child:IsA("Humanoid") then
return child
end
end
return nil
end
local function hasBothHandControllers()
return VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) and VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand)
end
local function hasAnyHandControllers()
return VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) or VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand)
end
local function isMobileVR()
return UserInputService.TouchEnabled
end
local function hasGamepad()
return UserInputService.GamepadEnabled
end
local function shouldUseNavigationLaser()
--Places where we use the navigation laser:
-- mobile VR with any number of hands tracked
-- desktop VR with only one hand tracked
-- desktop VR with no hands and no gamepad (i.e. with Oculus remote?)
--using an Xbox controller with a desktop VR headset means no laser since the user has a thumbstick.
--in the future, we should query thumbstick presence with a features API
if isMobileVR() then
return true
else
if hasBothHandControllers() then
return false
end
if not hasAnyHandControllers() then
return not hasGamepad()
end
return true
end
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function IsFiniteVector3(vec3)
return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
end
local moving = false
local function startFollowingPath(newPath)
currentPath = newPath
currentPoints = currentPath:GetPointCoordinates()
currentPointIdx = 1
moving = true
timeReachedLastPoint = tick()
local humanoid = getLocalHumanoid()
if humanoid and humanoid.Torso and #currentPoints >= 1 then
local dist = (currentPoints[1] - humanoid.Torso.Position).magnitude
expectedTimeToNextPoint = dist / humanoid.WalkSpeed
end
movementUpdateEvent:Fire("targetPoint", currentDestination)
end
local function goToPoint(point)
currentPath = true
currentPoints = { point }
currentPointIdx = 1
moving = true
local humanoid = getLocalHumanoid()
local distance = (humanoid.Torso.Position - point).magnitude
local estimatedTimeRemaining = distance / humanoid.WalkSpeed
timeReachedLastPoint = tick()
expectedTimeToNextPoint = estimatedTimeRemaining
movementUpdateEvent:Fire("targetPoint", point)
end
local function stopFollowingPath()
currentPath = nil
currentPoints = nil
currentPointIdx = 0
moving = false
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = ZERO_VECTOR3
end
local function tryComputePath(startPos, destination)
local numAttempts = 0
local newPath = nil
while not newPath and numAttempts < 5 do
newPath = PathfindingService:ComputeSmoothPathAsync(startPos, destination, MAX_PATHING_DISTANCE)
numAttempts = numAttempts + 1
if newPath.Status == Enum.PathStatus.ClosestNoPath or newPath.Status == Enum.PathStatus.ClosestOutOfRange then
newPath = nil
break
end
if newPath and newPath.Status == Enum.PathStatus.FailStartNotEmpty then
startPos = startPos + (destination - startPos).unit
newPath = nil
end
if newPath and newPath.Status == Enum.PathStatus.FailFinishNotEmpty then
destination = destination + Vector3.new(0, 1, 0)
newPath = nil
end
end
return newPath
end
local function onNavigationRequest(destinationCFrame, requestedWith)
local destinationPosition = destinationCFrame.p
local lastDestination = currentDestination
if not IsFiniteVector3(destinationPosition) then
return
end
currentDestination = destinationPosition
local humanoid = getLocalHumanoid()
if not humanoid or not humanoid.Torso then
return
end
local currentPosition = humanoid.Torso.Position
local distanceToDestination = (currentDestination - currentPosition).magnitude
if distanceToDestination < NO_PATH_THRESHOLD then
goToPoint(currentDestination)
return
end
if not lastDestination or (currentDestination - lastDestination).magnitude > RECALCULATE_PATH_THRESHOLD then
local newPath = tryComputePath(currentPosition, currentDestination)
if newPath then
startFollowingPath(newPath)
if PathDisplay then
PathDisplay.setCurrentPoints(currentPoints)
PathDisplay.renderPath()
end
else
stopFollowingPath()
if PathDisplay then
PathDisplay.clearRenderedPath()
end
end
else
if moving then
currentPoints[#currentPoints] = currentDestination
else
goToPoint(currentDestination)
end
end
end
local isJumpBound = false
local function onJumpAction(actionName, inputState, inputObj)
if inputState == Enum.UserInputState.Begin then
MasterControl:DoJump()
end
end
local function bindJumpAction(active)
if active then
if not isJumpBound then
isJumpBound = true
ContextActionService:BindAction("VRJumpAction", onJumpAction, false, Enum.KeyCode.ButtonA)
end
else
if isJumpBound then
isJumpBound = false
ContextActionService:UnbindAction("VRJumpAction")
end
end
end
local moveLatch = false
local controlCharacterGamepad = function(actionName, inputState, inputObject)
if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end
if inputState == Enum.UserInputState.Cancel then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
return
end
if inputState ~= Enum.UserInputState.End then
stopFollowingPath()
if PathDisplay then
PathDisplay.clearRenderedPath()
end
if shouldUseNavigationLaser() then
bindJumpAction(true)
setLaserPointerMode("Hidden")
end
if inputObject.Position.magnitude > THUMBSTICK_DEADZONE then
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(inputObject.Position.X, 0, -inputObject.Position.Y)
if currentMoveVector.magnitude > 0 then
currentMoveVector = currentMoveVector.unit * math.min(1, inputObject.Position.magnitude)
end
MasterControl:AddToPlayerMovement(currentMoveVector)
moveLatch = true
end
else
MasterControl:AddToPlayerMovement(-currentMoveVector)
currentMoveVector = Vector3.new(0,0,0)
if shouldUseNavigationLaser() then
bindJumpAction(false)
setLaserPointerMode("Navigation")
end
if moveLatch then
moveLatch = false
movementUpdateEvent:Fire("offtrack")
end
end
end
local function onHeartbeat(dt)
local newMoveVector = currentMoveVector
local humanoid = getLocalHumanoid()
if not humanoid or not humanoid.Torso then
return
end
if moving and currentPoints then
local currentPosition = humanoid.Torso.Position
local goalPosition = currentPoints[1]
local vectorToGoal = (goalPosition - currentPosition) * XZ_VECTOR3
local moveDist = vectorToGoal.magnitude
local moveDir = vectorToGoal / moveDist
if moveDist < POINT_REACHED_THRESHOLD then
local estimatedTimeRemaining = 0
local prevPoint = currentPoints[1]
for i, point in pairs(currentPoints) do
if i ~= 1 then
local dist = (point - prevPoint).magnitude
prevPoint = point
estimatedTimeRemaining = estimatedTimeRemaining + (dist / humanoid.WalkSpeed)
end
end
table.remove(currentPoints, 1)
currentPointIdx = currentPointIdx + 1
if #currentPoints == 0 then
stopFollowingPath()
if PathDisplay then
PathDisplay.clearRenderedPath()
end
return
else
if PathDisplay then
PathDisplay.setCurrentPoints(currentPoints)
PathDisplay.renderPath()
end
local newGoal = currentPoints[1]
local distanceToGoal = (newGoal - currentPosition).magnitude
expectedTimeToNextPoint = distanceToGoal / humanoid.WalkSpeed
timeReachedLastPoint = tick()
end
else
local ignoreTable = {
game.Players.LocalPlayer.Character,
workspace.CurrentCamera
}
local obstructRay = Ray.new(currentPosition - Vector3.new(0, 1, 0), moveDir * 3)
local obstructPart, obstructPoint, obstructNormal = workspace:FindPartOnRayWithIgnoreList(obstructRay, ignoreTable)
if obstructPart then
local heightOffset = Vector3.new(0, 100, 0)
local jumpCheckRay = Ray.new(obstructPoint + moveDir * 0.5 + heightOffset, -heightOffset)
local jumpCheckPart, jumpCheckPoint, jumpCheckNormal = workspace:FindPartOnRayWithIgnoreList(jumpCheckRay, ignoreTable)
local heightDifference = jumpCheckPoint.Y - currentPosition.Y
if heightDifference < 6 and heightDifference > -2 then
humanoid.Jump = true
end
end
local timeSinceLastPoint = tick() - timeReachedLastPoint
if timeSinceLastPoint > expectedTimeToNextPoint + OFFTRACK_TIME_THRESHOLD then
stopFollowingPath()
if PathDisplay then
PathDisplay.clearRenderedPath()
end
movementUpdateEvent:Fire("offtrack")
end
newMoveVector = currentMoveVector:Lerp(moveDir, dt * 10)
end
end
if IsFiniteVector3(newMoveVector) then
MasterControl:AddToPlayerMovement(newMoveVector - currentMoveVector)
currentMoveVector = newMoveVector
end
end
local userCFrameEnabledConn = nil
local function onUserCFrameEnabled()
if shouldUseNavigationLaser() then
bindJumpAction(false)
setLaserPointerMode("Navigation")
else
bindJumpAction(true)
setLaserPointerMode("Hidden")
end
end
function VRNavigation:Enable()
navigationRequestedConn = VRService.NavigationRequested:connect(onNavigationRequest)
heartbeatConn = RunService.Heartbeat:connect(onHeartbeat)
ContextActionService:BindAction("MoveThumbstick", controlCharacterGamepad, false, Enum.KeyCode.Thumbstick1)
ContextActionService:BindActivate(Enum.UserInputType.Gamepad1, Enum.KeyCode.ButtonR2)
userCFrameEnabledConn = VRService.UserCFrameEnabled:connect(onUserCFrameEnabled)
onUserCFrameEnabled()
pcall(function()
VRService:SetTouchpadMode(Enum.VRTouchpad.Left, Enum.VRTouchpadMode.VirtualThumbstick)
VRService:SetTouchpadMode(Enum.VRTouchpad.Right, Enum.VRTouchpadMode.ABXY)
end)
end
function VRNavigation:Disable()
stopFollowingPath()
ContextActionService:UnbindAction("MoveThumbstick")
ContextActionService:UnbindActivate(Enum.UserInputType.Gamepad1, Enum.KeyCode.ButtonR2)
bindJumpAction(false)
setLaserPointerMode("Disabled")
if navigationRequestedConn then
navigationRequestedConn:disconnect()
navigationRequestedConn = nil
end
if heartbeatConn then
heartbeatConn:disconnect()
heartbeatConn = nil
end
if userCFrameEnabledConn then
userCFrameEnabledConn:disconnect()
userCFrameEnabledConn = nil
end
end
return VRNavigation
@@ -0,0 +1,180 @@
--[[
// FileName: VehicleControl
// Version 1.0
// Written by: jmargh
// Description: Implements in-game vehicle controls for all input devices
// NOTE: This works for basic vehicles (single vehicle seat). If you use custom VehicleSeat code,
// multiple VehicleSeats or your own implementation of a VehicleSeat this will not work.
--]]
local VehicleController = {}
local ContextActionService = game:GetService('ContextActionService')
local Players = game:GetService('Players')
local RunService = game:GetService('RunService')
local MasterControl = require(script.Parent)
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local CurrentVehicleSeat = nil
local CurrentThrottle = 0
local CurrentSteer = 0
local HumanoidSeatedCn = nil
local RenderSteppedCn = nil
local Accelerating = false
local Deccelerating = false
local TurningRight = false
local TurningLeft = false
-- Set this to true if you want to instead use the triggers for the throttle
local useTriggersForThrottle = true
-- Also set this to true if you want the thumbstick to not affect throttle, only triggers when a gamepad is conected
local onlyTriggersForThrottle = false
local function onThrottleAccel(actionName, inputState, inputObject)
MasterControl:AddToPlayerMovement(Vector3.new(0, 0, -CurrentThrottle))
CurrentThrottle = (inputState == Enum.UserInputState.End or Deccelerating) and 0 or -1
MasterControl:AddToPlayerMovement(Vector3.new(0, 0, CurrentThrottle))
Accelerating = not (inputState == Enum.UserInputState.End)
if (inputState == Enum.UserInputState.End) and Deccelerating then
CurrentThrottle = 1
MasterControl:AddToPlayerMovement(Vector3.new(0, 0, CurrentThrottle))
end
end
local function onThrottleDeccel(actionName, inputState, inputObject)
MasterControl:AddToPlayerMovement(Vector3.new(0, 0, -CurrentThrottle))
CurrentThrottle = (inputState == Enum.UserInputState.End or Accelerating) and 0 or 1
MasterControl:AddToPlayerMovement(Vector3.new(0, 0, CurrentThrottle))
Deccelerating = not (inputState == Enum.UserInputState.End)
if (inputState == Enum.UserInputState.End) and Accelerating then
CurrentThrottle = -1
MasterControl:AddToPlayerMovement(Vector3.new(0, 0, CurrentThrottle))
end
end
local function onSteerRight(actionName, inputState, inputObject)
MasterControl:AddToPlayerMovement(Vector3.new(-CurrentSteer, 0, 0))
CurrentSteer = (inputState == Enum.UserInputState.End or TurningLeft) and 0 or 1
MasterControl:AddToPlayerMovement(Vector3.new(CurrentSteer, 0, 0))
TurningRight = not (inputState == Enum.UserInputState.End)
if (inputState == Enum.UserInputState.End) and TurningLeft then
CurrentSteer = -1
MasterControl:AddToPlayerMovement(Vector3.new(CurrentSteer, 0, 0))
end
end
local function onSteerLeft(actionName, inputState, inputObject)
MasterControl:AddToPlayerMovement(Vector3.new(-CurrentSteer, 0, 0))
CurrentSteer = (inputState == Enum.UserInputState.End or TurningRight) and 0 or -1
MasterControl:AddToPlayerMovement(Vector3.new(CurrentSteer, 0, 0))
TurningLeft = not (inputState == Enum.UserInputState.End)
if (inputState == Enum.UserInputState.End) and TurningRight then
CurrentSteer = 1
MasterControl:AddToPlayerMovement(Vector3.new(CurrentSteer, 0, 0))
end
end
local function getHumanoid()
local character = LocalPlayer and LocalPlayer.Character
if character then
for _,child in pairs(character:GetChildren()) do
if child:IsA('Humanoid') then
return child
end
end
end
end
local function getClosestFittingValue(value)
if value > 0.5 then
return 1
elseif value < -0.5 then
return -1
end
return 0
end
local function onRenderStepped()
if CurrentVehicleSeat then
local moveValue = MasterControl:GetMoveVector()
local didSetThrottleSteerFloat = false
didSetThrottleSteerFloat = pcall(function()
if game:GetService("UserInputService"):GetGamepadConnected(Enum.UserInputType.Gamepad1) and onlyTriggersForThrottle and useTriggersForThrottle then
CurrentVehicleSeat.ThrottleFloat = -CurrentThrottle
else
CurrentVehicleSeat.ThrottleFloat = -moveValue.z
end
CurrentVehicleSeat.SteerFloat = moveValue.x
end)
if didSetThrottleSteerFloat == false then
if game:GetService("UserInputService"):GetGamepadConnected(Enum.UserInputType.Gamepad1) and onlyTriggersForThrottle and useTriggersForThrottle then
CurrentVehicleSeat.Throttle = -CurrentThrottle
else
CurrentVehicleSeat.Throttle = getClosestFittingValue(-moveValue.z)
end
CurrentVehicleSeat.Steer = getClosestFittingValue(moveValue.x)
end
end
end
local function onSeated(active, currentSeatPart)
if active then
if currentSeatPart and currentSeatPart:IsA('VehicleSeat') then
CurrentVehicleSeat = currentSeatPart
if useTriggersForThrottle then
ContextActionService:BindAction("throttleAccel", onThrottleAccel, false, Enum.KeyCode.ButtonR2)
ContextActionService:BindAction("throttleDeccel", onThrottleDeccel, false, Enum.KeyCode.ButtonL2)
end
ContextActionService:BindAction("arrowSteerRight", onSteerRight, false, Enum.KeyCode.Right)
ContextActionService:BindAction("arrowSteerLeft", onSteerLeft, false, Enum.KeyCode.Left)
local success = pcall(function() RunService:BindToRenderStep("VehicleControlStep", Enum.RenderPriority.Input.Value, onRenderStepped) end)
if not success then
if RenderSteppedCn then return end
RenderSteppedCn = RunService.RenderStepped:connect(onRenderStepped)
end
end
else
CurrentVehicleSeat = nil
if useTriggersForThrottle then
ContextActionService:UnbindAction("throttleAccel")
ContextActionService:UnbindAction("throttleDeccel")
end
ContextActionService:UnbindAction("arrowSteerRight")
ContextActionService:UnbindAction("arrowSteerLeft")
MasterControl:AddToPlayerMovement(Vector3.new(-CurrentSteer, 0, -CurrentThrottle))
CurrentThrottle = 0
CurrentSteer = 0
local success = pcall(function() RunService:UnbindFromRenderStep("VehicleControlStep") end)
if not success and RenderSteppedCn then
RenderSteppedCn:disconnect()
RenderSteppedCn = nil
end
end
end
local function CharacterAdded(character)
local humanoid = getHumanoid()
while not humanoid do
wait()
humanoid = getHumanoid()
end
--
if HumanoidSeatedCn then
HumanoidSeatedCn:disconnect()
HumanoidSeatedCn = nil
end
HumanoidSeatedCn = humanoid.Seated:connect(onSeated)
end
if LocalPlayer.Character then
CharacterAdded(LocalPlayer.Character)
end
LocalPlayer.CharacterAdded:connect(CharacterAdded)
return VehicleController
@@ -0,0 +1,131 @@
local PathDisplay = {}
PathDisplay.spacing = 8
PathDisplay.image = "rbxasset://textures/Cursors/Gamepad/Pointer.png"
PathDisplay.imageSize = Vector2.new(2, 2)
local currentPoints = {}
local renderedPoints = {}
local pointModel = Instance.new("Model")
pointModel.Name = "PathDisplayPoints"
local adorneePart = Instance.new("Part")
adorneePart.Anchored = true
adorneePart.CanCollide = false
adorneePart.Transparency = 1
adorneePart.Name = "PathDisplayAdornee"
adorneePart.CFrame = CFrame.new(0, 0, 0)
adorneePart.Parent = pointModel
local pointPool = {}
local poolTop = 30
for i = 1, poolTop do
local point = Instance.new("ImageHandleAdornment")
point.Archivable = false
point.Adornee = adorneePart
point.Image = PathDisplay.image
point.Size = PathDisplay.imageSize
pointPool[i] = point
end
local function retrieveFromPool()
local point = pointPool[1]
if not point then
return
end
pointPool[1], pointPool[poolTop] = pointPool[poolTop], nil
poolTop = poolTop - 1
return point
end
local function returnToPool(point)
poolTop = poolTop + 1
pointPool[poolTop] = point
end
local function renderPoint(point, isLast)
if poolTop == 0 then
return
end
local rayDown = Ray.new(point + Vector3.new(0, 2, 0), Vector3.new(0, -8, 0))
local hitPart, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(rayDown, { game.Players.LocalPlayer.Character, workspace.CurrentCamera })
if not hitPart then
return
end
local pointCFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
local point = retrieveFromPool()
point.CFrame = pointCFrame
point.Parent = pointModel
return point
end
function PathDisplay.setCurrentPoints(points)
if typeof(points) == 'table' then
currentPoints = points
else
currentPoints = {}
end
end
function PathDisplay.clearRenderedPath()
for _, oldPoint in ipairs(renderedPoints) do
oldPoint.Parent = nil
returnToPool(oldPoint)
end
renderedPoints = {}
pointModel.Parent = nil
end
function PathDisplay.renderPath()
PathDisplay.clearRenderedPath()
if not currentPoints or #currentPoints == 0 then
return
end
local currentIdx = #currentPoints
local lastPos = currentPoints[currentIdx]
local distanceBudget = 0
renderedPoints[1] = renderPoint(lastPos, true)
if not renderedPoints[1] then
return
end
while true do
local currentPoint = currentPoints[currentIdx]
local nextPoint = currentPoints[currentIdx - 1]
if currentIdx < 2 then
break
else
local toNextPoint = nextPoint - currentPoint
local distToNextPoint = toNextPoint.magnitude
if distanceBudget > distToNextPoint then
distanceBudget = distanceBudget - distToNextPoint
currentIdx = currentIdx - 1
else
local dirToNextPoint = toNextPoint.unit
local pointPos = currentPoint + (dirToNextPoint * distanceBudget)
local point = renderPoint(pointPos, false)
if point then
renderedPoints[#renderedPoints + 1] = point
end
distanceBudget = distanceBudget + PathDisplay.spacing
end
end
end
pointModel.Parent = workspace.CurrentCamera
end
return PathDisplay