SyntaxGameServer/RCCService2018/content/scripts/PlayerScripts/StarterPlayerScripts/CameraScript/RootCamera.lua

1536 lines
46 KiB
Lua

local PlayersService = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local StarterGui = game:GetService('StarterGui')
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService('ContextActionService')
local VRService = game:GetService("VRService")
local LocalPlayer = PlayersService.LocalPlayer
local PlayerGui = nil
if LocalPlayer then
PlayerGui = PlayersService.LocalPlayer:WaitForChild("PlayerGui")
end
local PortraitMode = false
local CameraScript = script.Parent
local ShiftLockController = require(CameraScript:WaitForChild('ShiftLockController'))
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
local function clamp(low, high, num)
return (num > high and high or num < low and low or num)
end
local math_atan2 = math.atan2
local function findAngleBetweenXZVectors(vec2, vec1)
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local THUMBSTICK_DEADZONE = 0.2
local LANDSCAPE_DEFAULT_ZOOM = 12.5
local PORTRAIT_DEFAULT_ZOOM = 25
local humanoidCache = {}
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoidCache[player] = humanoid
end
return humanoid
end
end
end
local MIN_Y = math.rad(-80)
local MAX_Y = math.rad(80)
local DEFAULT_CAMERA_ANGLE = 25
local VR_ANGLE = math.rad(15)
local VR_LOW_INTENSITY_ROTATION = Vector2.new(math.rad(15), 0)
local VR_HIGH_INTENSITY_ROTATION = Vector2.new(math.rad(45), 0)
local VR_LOW_INTENSITY_REPEAT = 0.1
local VR_HIGH_INTENSITY_REPEAT = 0.4
local ZERO_VECTOR2 = Vector2.new(0, 0)
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local TOUCH_SENSITIVTY = Vector2.new(math.pi*2.25, math.pi*2)
local MOUSE_SENSITIVITY = Vector2.new(math.pi*4, math.pi*1.9)
local MAX_TIME_FOR_DOUBLE_TAP = 1.5
local MAX_TAP_POS_DELTA = 15
local MAX_TAP_TIME_DELTA = 0.75
local SEAT_OFFSET = Vector3.new(0,5,0)
local VR_SEAT_OFFSET = Vector3.new(0, 4, 0)
local HEAD_OFFSET = Vector3.new(0, 1.5, 0)
local R15_HEAD_OFFSET = Vector3.new(0, 2.0, 0)
local PORTRAIT_MODE_CAMERA_OFFSET = 2
-- Reset the camera look vector when the camera is enabled for the first time
local SetCameraOnSpawn = true
local hasGameLoaded = false
local GestureArea = nil
local GestureAreaManagedByControlScript = false
local function layoutGestureArea(portraitMode)
if GestureArea and not GestureAreaManagedByControlScript then
if portraitMode then
GestureArea.Size = UDim2.new(1, 0, .6, 0)
GestureArea.Position = UDim2.new(0, 0, 0, 0)
else
GestureArea.Size = UDim2.new(1, 0, .5, -18)
GestureArea.Position = UDim2.new(0, 0, 0, 0)
end
end
end
-- Setup gesture area that camera uses while DynamicThumbstick is enabled
local function OnCharacterAdded(character)
if UserInputService.TouchEnabled then
for _, child in ipairs(LocalPlayer.Character:GetChildren()) do
if child:IsA("Tool") then
IsAToolEquipped = true
end
end
character.ChildAdded:Connect(function(child)
if child:IsA("Tool") then
IsAToolEquipped = true
end
end)
character.ChildRemoved:Connect(function(child)
if child:IsA("Tool") then
IsAToolEquipped = false
end
end)
if PlayerGui then
local TouchGui = PlayerGui:FindFirstChild("TouchGui")
if TouchGui and TouchGui:WaitForChild("GestureArea", 0.5) then
GestureArea = TouchGui.GestureArea
GestureAreaManagedByControlScript = true
else
GestureAreaManagedByControlScript = false
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Name = "GestureArea"
ScreenGui.Parent = PlayerGui
GestureArea = Instance.new("Frame")
GestureArea.BackgroundTransparency = 1.0
GestureArea.Visible = true
GestureArea.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
layoutGestureArea(PortraitMode)
GestureArea.Parent = ScreenGui
end
end
end
end
if LocalPlayer then
if LocalPlayer.Character ~= nil then
OnCharacterAdded(LocalPlayer.Character)
end
LocalPlayer.CharacterAdded:connect(function(character)
OnCharacterAdded(character)
end)
end
local function positionIntersectsGuiObject(position, guiObject)
if position.X < guiObject.AbsolutePosition.X + guiObject.AbsoluteSize.X
and position.X > guiObject.AbsolutePosition.X
and position.Y < guiObject.AbsolutePosition.Y + guiObject.AbsoluteSize.Y
and position.Y > guiObject.AbsolutePosition.Y then
return true
end
return false
end
local function GetRenderCFrame(part)
return part:GetRenderCFrame()
end
local function CreateCamera()
local this = {}
local R15HeadHeight = R15_HEAD_OFFSET
function this:GetActivateValue()
return 0.7
end
function this:IsPortraitMode()
return PortraitMode
end
function this:GetRotateAmountValue(vrRotationIntensity)
vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
if vrRotationIntensity then
if vrRotationIntensity == "Low" then
return VR_LOW_INTENSITY_ROTATION
elseif vrRotationIntensity == "High" then
return VR_HIGH_INTENSITY_ROTATION
end
end
return ZERO_VECTOR2
end
function this:GetRepeatDelayValue(vrRotationIntensity)
vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
if vrRotationIntensity then
if vrRotationIntensity == "Low" then
return VR_LOW_INTENSITY_REPEAT
elseif vrRotationIntensity == "High" then
return VR_HIGH_INTENSITY_REPEAT
end
end
return 0
end
this.ShiftLock = false
this.Enabled = false
local isFirstPerson = false
local isRightMouseDown = false
local isMiddleMouseDown = false
this.RotateInput = ZERO_VECTOR2
this.DefaultZoom = LANDSCAPE_DEFAULT_ZOOM
this.activeGamepad = nil
local tweens = {}
this.lastSubject = nil
this.lastSubjectPosition = Vector3.new(0, 5, 0)
local lastVRRotation = 0
local vrRotateKeyCooldown = {}
local isDynamicThumbstickEnabled = false
local dynamicThumbstickFrame = nil
local function getDynamicThumbstickFrame()
if dynamicThumbstickFrame and dynamicThumbstickFrame:IsDescendantOf(game) then
return dynamicThumbstickFrame
else
local touchGui = PlayerGui:FindFirstChild("TouchGui")
if not touchGui then return nil end
local touchControlFrame = touchGui:FindFirstChild("TouchControlFrame")
if not touchControlFrame then return nil end
dynamicThumbstickFrame = touchControlFrame:FindFirstChild("DynamicThumbstickFrame")
return dynamicThumbstickFrame
end
end
-- Check for changes in ViewportSize to decide if PortraitMode
local CameraChangedConn = nil
local workspaceCameraChangedConn = nil
local function onWorkspaceCameraChanged()
if UserInputService.TouchEnabled then
if CameraChangedConn then
CameraChangedConn:Disconnect()
CameraChangedConn = nil
end
local newCamera = workspace.CurrentCamera
if newCamera then
local size = newCamera.ViewportSize
PortraitMode = size.X < size.Y
layoutGestureArea(PortraitMode)
DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
size = newCamera.ViewportSize
PortraitMode = size.X < size.Y
layoutGestureArea(PortraitMode)
DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
end)
end
end
end
workspaceCameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onWorkspaceCameraChanged)
if workspace.CurrentCamera then
onWorkspaceCameraChanged()
end
function this:GetShiftLock()
return ShiftLockController:IsShiftLocked()
end
function this:GetHumanoid()
local player = PlayersService.LocalPlayer
return findPlayerHumanoid(player)
end
function this:GetHumanoidRootPart()
local humanoid = this:GetHumanoid()
return humanoid and humanoid.Torso
end
function this:GetRenderCFrame(part)
GetRenderCFrame(part)
end
local STATE_DEAD = Enum.HumanoidStateType.Dead
-- HumanoidRootPart when alive, Head part when dead
local function getHumanoidPartToFollow(humanoid, humanoidStateType)
if humanoidStateType == STATE_DEAD then
local character = humanoid.Parent
if character then
return character:FindFirstChild("Head") or humanoid.Torso
else
return humanoid.Torso
end
else
return humanoid.Torso
end
end
local HUMANOID_STATE_DEAD = Enum.HumanoidStateType.Dead
function this:GetSubjectPosition()
local result = nil
local camera = workspace.CurrentCamera
local cameraSubject = camera and camera.CameraSubject
if cameraSubject then
if cameraSubject:IsA('Humanoid') then
local humanoidStateType = cameraSubject:GetState()
if VRService.VREnabled and humanoidStateType == STATE_DEAD and cameraSubject == this.lastSubject then
result = this.lastSubjectPosition
else
local humanoidRootPart = getHumanoidPartToFollow(cameraSubject, humanoidStateType)
if humanoidRootPart and humanoidRootPart:IsA('BasePart') then
local subjectCFrame = GetRenderCFrame(humanoidRootPart)
local heightOffset = ZERO_VECTOR3
if humanoidStateType ~= STATE_DEAD then
heightOffset = cameraSubject.RigType == Enum.HumanoidRigType.R15 and R15HeadHeight or HEAD_OFFSET
end
if PortraitMode then
heightOffset = heightOffset + Vector3.new(0, PORTRAIT_MODE_CAMERA_OFFSET, 0)
end
result = subjectCFrame.p +
subjectCFrame:vectorToWorldSpace(heightOffset + cameraSubject.CameraOffset)
end
end
elseif cameraSubject:IsA('VehicleSeat') then
local subjectCFrame = GetRenderCFrame(cameraSubject)
local offset = SEAT_OFFSET
if VRService.VREnabled then
offset = VR_SEAT_OFFSET
end
result = subjectCFrame.p + subjectCFrame:vectorToWorldSpace(offset)
elseif cameraSubject:IsA('SkateboardPlatform') then
local subjectCFrame = GetRenderCFrame(cameraSubject)
result = subjectCFrame.p + SEAT_OFFSET
elseif cameraSubject:IsA('BasePart') then
local subjectCFrame = GetRenderCFrame(cameraSubject)
result = subjectCFrame.p
elseif cameraSubject:IsA('Model') then
result = cameraSubject:GetModelCFrame().p
end
end
this.lastSubject = cameraSubject
this.lastSubjectPosition = result
return result
end
function this:ResetCameraLook()
end
function this:GetCameraLook()
return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)
end
function this:GetCameraZoom()
if this.currentZoom == nil then
local player = PlayersService.LocalPlayer
this.currentZoom = player and clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, this.DefaultZoom) or this.DefaultZoom
end
return this.currentZoom
end
function this:GetCameraActualZoom()
local camera = workspace.CurrentCamera
if camera then
return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
end
end
function this:GetCameraHeight()
if VRService.VREnabled and not this:IsInFirstPerson() then
local zoom = this:GetCameraZoom()
return math.sin(VR_ANGLE) * zoom
end
return 0
end
function this:ViewSizeX()
local result = 1024
local camera = workspace.CurrentCamera
if camera then
result = camera.ViewportSize.X
end
return result
end
function this:ViewSizeY()
local result = 768
local camera = workspace.CurrentCamera
if camera then
result = camera.ViewportSize.Y
end
return result
end
local math_asin = math.asin
local math_atan2 = math.atan2
local math_floor = math.floor
local math_max = math.max
local math_pi = math.pi
local Vector2_new = Vector2.new
local Vector3_new = Vector3.new
local CFrame_Angles = CFrame.Angles
local CFrame_new = CFrame.new
function this:ScreenTranslationToAngle(translationVector)
local screenX = this:ViewSizeX()
local screenY = this:ViewSizeY()
local xTheta = (translationVector.x / screenX)
local yTheta = (translationVector.y / screenY)
return Vector2_new(xTheta, yTheta)
end
function this:MouseTranslationToAngle(translationVector)
local xTheta = (translationVector.x / 1920)
local yTheta = (translationVector.y / 1200)
return Vector2_new(xTheta, yTheta)
end
function this:RotateVector(startVector, xyRotateVector)
local startCFrame = CFrame_new(ZERO_VECTOR3, startVector)
local resultLookVector = (CFrame_Angles(0, -xyRotateVector.x, 0) * startCFrame * CFrame_Angles(-xyRotateVector.y,0,0)).lookVector
return resultLookVector, Vector2_new(xyRotateVector.x, xyRotateVector.y)
end
function this:RotateCamera(startLook, xyRotateVector)
if VRService.VREnabled then
local yawRotatedVector, xyRotateVector = self:RotateVector(startLook, Vector2.new(xyRotateVector.x, 0))
return Vector3_new(yawRotatedVector.x, 0, yawRotatedVector.z).unit, xyRotateVector
else
local startVertical = math_asin(startLook.y)
local yTheta = clamp(-MAX_Y + startVertical, -MIN_Y + startVertical, xyRotateVector.y)
return self:RotateVector(startLook, Vector2_new(xyRotateVector.x, yTheta))
end
end
function this:IsInFirstPerson()
return isFirstPerson
end
-- there are several cases to consider based on the state of input and camera rotation mode
function this:UpdateMouseBehavior()
-- first time transition to first person mode or shiftlock
local camera = workspace.CurrentCamera
if camera.CameraType == Enum.CameraType.Scriptable then
return
end
if isFirstPerson or self:GetShiftLock() then
pcall(function() GameSettings.RotationType = Enum.RotationType.CameraRelative end)
if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
end
else
pcall(function() GameSettings.RotationType = Enum.RotationType.MovementRelative end)
if isRightMouseDown or isMiddleMouseDown then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
function this:ZoomCamera(desiredZoom)
local player = PlayersService.LocalPlayer
if player then
if player.CameraMode == Enum.CameraMode.LockFirstPerson then
this.currentZoom = 0
else
this.currentZoom = clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredZoom)
end
end
isFirstPerson = self:GetCameraZoom() < 2
ShiftLockController:SetIsInFirstPerson(isFirstPerson)
-- set mouse behavior
self:UpdateMouseBehavior()
return self:GetCameraZoom()
end
function this:rk4Integrator(position, velocity, t)
local direction = velocity < 0 and -1 or 1
local function acceleration(p, v)
local accel = direction * math_max(1, (p / 3.3) + 0.5)
return accel
end
local p1 = position
local v1 = velocity
local a1 = acceleration(p1, v1)
local p2 = p1 + v1 * (t / 2)
local v2 = v1 + a1 * (t / 2)
local a2 = acceleration(p2, v2)
local p3 = p1 + v2 * (t / 2)
local v3 = v1 + a2 * (t / 2)
local a3 = acceleration(p3, v3)
local p4 = p1 + v3 * t
local v4 = v1 + a3 * t
local a4 = acceleration(p4, v4)
local positionResult = position + (v1 + 2 * v2 + 2 * v3 + v4) * (t / 6)
local velocityResult = velocity + (a1 + 2 * a2 + 2 * a3 + a4) * (t / 6)
return positionResult, velocityResult
end
function this:ZoomCameraBy(zoomScale)
local zoom = this:GetCameraActualZoom()
if zoom then
-- Can break into more steps to get more accurate integration
zoom = self:rk4Integrator(zoom, zoomScale, 1)
self:ZoomCamera(zoom)
end
return self:GetCameraZoom()
end
function this:ZoomCameraFixedBy(zoomIncrement)
return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
end
function this:Update()
end
----- VR STUFF ------
function this:ApplyVRTransform()
if not VRService.VREnabled then
return
end
--we only want this to happen in first person VR
local player = PlayersService.LocalPlayer
if not (player and player.Character
and player.Character:FindFirstChild("HumanoidRootPart")
and player.Character.HumanoidRootPart:FindFirstChild("RootJoint")) then
return
end
local camera = workspace.CurrentCamera
local cameraSubject = camera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
if this:IsInFirstPerson() and not isInVehicle then
local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
local vrRotation = vrFrame - vrFrame.p
local rootJoint = player.Character.HumanoidRootPart.RootJoint
rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
else
local rootJoint = player.Character.HumanoidRootPart.RootJoint
rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
end
end
local vrRotationIntensityExists = true
local lastVrRotationCheck = 0
function this:ShouldUseVRRotation()
if not VRService.VREnabled then
return false
end
if not vrRotationIntensityExists and tick() - lastVrRotationCheck < 1 then return false end
local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
vrRotationIntensityExists = success and vrRotationIntensity ~= nil
lastVrRotationCheck = tick()
return success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
end
function this:GetVRRotationInput()
local vrRotateSum = ZERO_VECTOR2
local vrRotationIntensity = StarterGui:GetCore("VRRotationIntensity")
local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
local delayExpired = (tick() - lastVRRotation) >= self:GetRepeatDelayValue(vrRotationIntensity)
if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
local sign = 1
if vrGamepadRotation.x < 0 then
sign = -1
end
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
end
elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
end
if self.TurningLeft then
if delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Left] then
vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
vrRotateKeyCooldown[Enum.KeyCode.Left] = true
end
else
vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
end
if self.TurningRight then
if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Right]) then
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
vrRotateKeyCooldown[Enum.KeyCode.Right] = true
end
else
vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
end
if vrRotateSum ~= ZERO_VECTOR2 then
lastVRRotation = tick()
end
return vrRotateSum
end
local cameraTranslationConstraints = Vector3.new(1, 1, 1)
local humanoidJumpOrigin = nil
local trackingHumanoid = nil
local cameraFrozen = false
local subjectStateChangedConn = nil
local cameraSubjectChangedConn = nil
local workspaceChangedConn = nil
local humanoidChildAddedConn = nil
local humanoidChildRemovedConn = nil
local function cancelCameraFreeze(keepConstraints)
if not keepConstraints then
cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 1, cameraTranslationConstraints.z)
end
if cameraFrozen then
trackingHumanoid = nil
cameraFrozen = false
end
end
local function startCameraFreeze(subjectPosition, humanoidToTrack)
if not cameraFrozen then
humanoidJumpOrigin = subjectPosition
trackingHumanoid = humanoidToTrack
cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 0, cameraTranslationConstraints.z)
cameraFrozen = true
end
end
local function rescaleCameraOffset(newScaleFactor)
R15HeadHeight = R15_HEAD_OFFSET*newScaleFactor
end
local function onHumanoidSubjectChildAdded(child)
if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
if heightScaleChangedConn then
heightScaleChangedConn:disconnect()
end
heightScaleChangedConn = child.Changed:connect(rescaleCameraOffset)
rescaleCameraOffset(child.Value)
end
end
local function onHumanoidSubjectChildRemoved(child)
if child.Name == "BodyHeightScale" then
rescaleCameraOffset(1)
if heightScaleChangedConn then
heightScaleChangedConn:disconnect()
heightScaleChangedConn = nil
end
end
end
local function onNewCameraSubject()
if subjectStateChangedConn then
subjectStateChangedConn:disconnect()
subjectStateChangedConn = nil
end
if humanoidChildAddedConn then
humanoidChildAddedConn:disconnect()
humanoidChildAddedConn = nil
end
if humanoidChildRemovedConn then
humanoidChildRemovedConn:disconnect()
humanoidChildRemovedConn = nil
end
if heightScaleChangedConn then
heightScaleChangedConn:disconnect()
heightScaleChangedConn = nil
end
local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
if trackingHumanoid ~= humanoid then
cancelCameraFreeze()
end
if humanoid and humanoid:IsA('Humanoid') then
humanoidChildAddedConn = humanoid.ChildAdded:connect(onHumanoidSubjectChildAdded)
humanoidChildRemovedConn = humanoid.ChildRemoved:connect(onHumanoidSubjectChildRemoved)
for _, child in pairs(humanoid:GetChildren()) do
onHumanoidSubjectChildAdded(child)
end
subjectStateChangedConn = humanoid.StateChanged:connect(function(oldState, newState)
if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not this:IsInFirstPerson() then
startCameraFreeze(this:GetSubjectPosition(), humanoid)
elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
cancelCameraFreeze(true)
end
end)
end
end
local function onCurrentCameraChanged()
if cameraSubjectChangedConn then
cameraSubjectChangedConn:disconnect()
cameraSubjectChangedConn = nil
end
local camera = workspace.CurrentCamera
if camera then
cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):connect(onNewCameraSubject)
onNewCameraSubject()
end
end
function this:GetVRFocus(subjectPosition, timeDelta)
local newFocus = nil
local camera = workspace.CurrentCamera
local lastFocus = self.LastCameraFocus or subjectPosition
if not cameraFrozen then
cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, math.min(1, cameraTranslationConstraints.y + 0.42 * timeDelta), cameraTranslationConstraints.z)
end
if cameraFrozen and humanoidJumpOrigin and humanoidJumpOrigin.y > lastFocus.y then
newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
else
newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, cameraTranslationConstraints.y))
end
if cameraFrozen then
-- No longer in 3rd person
if self:IsInFirstPerson() then -- not VRService.VREnabled
cancelCameraFreeze()
end
-- This case you jumped off a cliff and want to keep your character in view
-- 0.5 is to fix floating point error when not jumping off cliffs
if humanoidJumpOrigin and subjectPosition.y < (humanoidJumpOrigin.y - 0.5) then
cancelCameraFreeze()
end
end
return newFocus
end
------------------------
---- Input Events ----
local startPos = nil
local lastPos = nil
local panBeginLook = nil
local lastTapTime = nil
local fingerTouches = {}
local NumUnsunkTouches = 0
local inputStartPositions = {}
local inputStartTimes = {}
local StartingDiff = nil
local pinchBeginZoom = nil
this.ZoomEnabled = true
this.PanEnabled = true
this.KeyPanEnabled = true
local function OnTouchBegan(input, processed)
--If isDynamicThumbstickEnabled, then only process TouchBegan event if it starts in GestureArea
local dtFrame = getDynamicThumbstickFrame()
local isDynamicThumbstickUsingThisInput = false
if isDynamicThumbstickEnabled then
local ControlScript = CameraScript.Parent:FindFirstChild("ControlScript")
if ControlScript then
local MasterControl = ControlScript:FindFirstChild("MasterControl")
if MasterControl then
local DynamicThumbstickModule = MasterControl:FindFirstChild("DynamicThumbstick")
if DynamicThumbstickModule then
DynamicThumbstickModule = require(DynamicThumbstickModule)
local dynamicInputObject = DynamicThumbstickModule:GetInputObject()
isDynamicThumbstickUsingThisInput = (dynamicInputObject == input)
end
end
end
end
if not isDynamicThumbstickUsingThisInput then
fingerTouches[input] = processed
if not processed then
inputStartPositions[input] = input.Position
inputStartTimes[input] = tick()
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
end
local function OnTouchChanged(input, processed)
if fingerTouches[input] == nil then
if isDynamicThumbstickEnabled then
return
end
fingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
if NumUnsunkTouches == 1 then
if fingerTouches[input] == false then
panBeginLook = panBeginLook or this:GetCameraLook()
startPos = startPos or input.Position
lastPos = lastPos or startPos
this.UserPanningTheCamera = true
local delta = input.Position - lastPos
delta = Vector2.new(delta.X, delta.Y * GameSettings:GetCameraYInvertValue())
if this.PanEnabled then
local desiredXYVector = this:ScreenTranslationToAngle(delta) * TOUCH_SENSITIVTY
this.RotateInput = this.RotateInput + desiredXYVector
end
lastPos = input.Position
end
else
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
end
if NumUnsunkTouches == 2 then
local unsunkTouches = {}
for touch, wasSunk in pairs(fingerTouches) do
if not wasSunk then
table.insert(unsunkTouches, touch)
end
end
if #unsunkTouches == 2 then
local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
if StartingDiff and pinchBeginZoom then
local scale = difference / math_max(0.01, StartingDiff)
local clampedScale = clamp(0.1, 10, scale)
if this.ZoomEnabled then
this:ZoomCamera(pinchBeginZoom / clampedScale)
end
else
StartingDiff = difference
pinchBeginZoom = this:GetCameraActualZoom()
end
end
else
StartingDiff = nil
pinchBeginZoom = nil
end
end
local function calcLookBehindRotateInput(torso)
if torso then
local newDesiredLook = (torso.CFrame.lookVector - Vector3.new(0, math.sin(math.rad(DEFAULT_CAMERA_ANGLE), 0))).unit
local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
if not IsFinite(horizontalShift) then
horizontalShift = 0
end
if not IsFinite(vertShift) then
vertShift = 0
end
return Vector2.new(horizontalShift, vertShift)
end
return nil
end
local function IsTouchTap(input)
-- We can't make the assumption that the input exists in the inputStartPositions because we may have switched from a different camera type.
if inputStartPositions[input] then
local posDelta = (inputStartPositions[input] - input.Position).magnitude
if posDelta < MAX_TAP_POS_DELTA then
local timeDelta = inputStartTimes[input] - tick()
if timeDelta < MAX_TAP_TIME_DELTA then
return true
end
end
end
return false
end
local function OnTouchEnded(input, processed)
if fingerTouches[input] == false then
if NumUnsunkTouches == 1 then
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
elseif NumUnsunkTouches == 2 then
StartingDiff = nil
pinchBeginZoom = nil
end
end
if fingerTouches[input] ~= nil and fingerTouches[input] == false then
NumUnsunkTouches = NumUnsunkTouches - 1
end
fingerTouches[input] = nil
inputStartPositions[input] = nil
inputStartTimes[input] = nil
end
local function OnMousePanButtonPressed(input, processed)
if processed then return end
this:UpdateMouseBehavior()
panBeginLook = panBeginLook or this:GetCameraLook()
startPos = startPos or input.Position
lastPos = lastPos or startPos
this.UserPanningTheCamera = true
end
local function OnMousePanButtonReleased(input, processed)
this:UpdateMouseBehavior()
if not (isRightMouseDown or isMiddleMouseDown) then
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
end
end
local function OnMouse2Down(input, processed)
if processed then return end
isRightMouseDown = true
OnMousePanButtonPressed(input, processed)
end
local function OnMouse2Up(input, processed)
isRightMouseDown = false
OnMousePanButtonReleased(input, processed)
end
local function OnMouse3Down(input, processed)
if processed then return end
isMiddleMouseDown = true
OnMousePanButtonPressed(input, processed)
end
local function OnMouse3Up(input, processed)
isMiddleMouseDown = false
OnMousePanButtonReleased(input, processed)
end
local function OnMouseMoved(input, processed)
if not hasGameLoaded and VRService.VREnabled then
return
end
local inputDelta = input.Delta
inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * GameSettings:GetCameraYInvertValue())
if startPos and lastPos and panBeginLook then
local currPos = lastPos + input.Delta
local totalTrans = currPos - startPos
if this.PanEnabled then
local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
this.RotateInput = this.RotateInput + desiredXYVector
end
lastPos = currPos
elseif this:IsInFirstPerson() or this:GetShiftLock() then
if this.PanEnabled then
local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
this.RotateInput = this.RotateInput + desiredXYVector
end
end
end
local function OnMouseWheel(input, processed)
if not hasGameLoaded and VRService.VREnabled then
return
end
if not processed then
if this.ZoomEnabled then
this:ZoomCameraBy(clamp(-1, 1, -input.Position.Z) * 1.4)
end
end
end
local function round(num)
return math_floor(num + 0.5)
end
local eight2Pi = math_pi / 4
local function rotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
if camLook ~= ZERO_VECTOR3 then
camLook = camLook.unit
local currAngle = math_atan2(camLook.z, camLook.x)
local newAngle = round((math_atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
return newAngle - currAngle
end
return 0
end
local function OnKeyDown(input, processed)
if not hasGameLoaded and VRService.VREnabled then
return
end
if processed then return end
if this.ZoomEnabled then
if input.KeyCode == Enum.KeyCode.I then
this:ZoomCameraBy(-5)
elseif input.KeyCode == Enum.KeyCode.O then
this:ZoomCameraBy(5)
end
end
if panBeginLook == nil and this.KeyPanEnabled then
if input.KeyCode == Enum.KeyCode.Left then
this.TurningLeft = true
elseif input.KeyCode == Enum.KeyCode.Right then
this.TurningRight = true
elseif input.KeyCode == Enum.KeyCode.Comma then
local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), -eight2Pi * (3/4), eight2Pi)
if angle ~= 0 then
this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
this.LastUserPanCamera = tick()
this.LastCameraTransform = nil
end
elseif input.KeyCode == Enum.KeyCode.Period then
local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), eight2Pi * (3/4), eight2Pi)
if angle ~= 0 then
this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
this.LastUserPanCamera = tick()
this.LastCameraTransform = nil
end
elseif input.KeyCode == Enum.KeyCode.PageUp then
--elseif input.KeyCode == Enum.KeyCode.Home then
this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(15))
this.LastCameraTransform = nil
elseif input.KeyCode == Enum.KeyCode.PageDown then
--elseif input.KeyCode == Enum.KeyCode.End then
this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(-15))
this.LastCameraTransform = nil
end
end
end
local function OnKeyUp(input, processed)
if input.KeyCode == Enum.KeyCode.Left then
this.TurningLeft = false
elseif input.KeyCode == Enum.KeyCode.Right then
this.TurningRight = false
end
end
local function onWindowFocusReleased()
this:ResetInputStates()
end
local lastThumbstickRotate = nil
local numOfSeconds = 0.7
local currentSpeed = 0
local maxSpeed = 6
local vrMaxSpeed = 4
local lastThumbstickPos = Vector2.new(0,0)
local ySensitivity = 0.65
local lastVelocity = nil
-- K is a tunable parameter that changes the shape of the S-curve
-- the larger K is the more straight/linear the curve gets
local k = 0.35
local lowerK = 0.8
local function SCurveTranform(t)
t = clamp(-1,1,t)
if t >= 0 then
return (k*t) / (k - t + 1)
end
return -((lowerK*-t) / (lowerK + t + 1))
end
-- DEADZONE
local DEADZONE = 0.1
local function toSCurveSpace(t)
return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
end
local function fromSCurveSpace(t)
return t/2 + 0.5
end
local function gamepadLinearToCurve(thumbstickPosition)
local function onAxis(axisValue)
local sign = 1
if axisValue < 0 then
sign = -1
end
local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
point = point * sign
return clamp(-1, 1, point)
end
return Vector2_new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
end
function this:UpdateGamepad()
local gamepadPan = this.GamepadPanningCamera
if gamepadPan and (hasGameLoaded or not VRService.VREnabled) then
gamepadPan = gamepadLinearToCurve(gamepadPan)
local currentTime = tick()
if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
this.userPanningTheCamera = true
elseif gamepadPan == ZERO_VECTOR2 then
lastThumbstickRotate = nil
if lastThumbstickPos == ZERO_VECTOR2 then
currentSpeed = 0
end
end
local finalConstant = 0
if lastThumbstickRotate then
if VRService.VREnabled then
currentSpeed = vrMaxSpeed
else
local elapsedTime = (currentTime - lastThumbstickRotate) * 10
currentSpeed = currentSpeed + (maxSpeed * ((elapsedTime*elapsedTime)/numOfSeconds))
if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
if lastVelocity then
local velocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
local velocityDeltaMag = (velocity - lastVelocity).magnitude
if velocityDeltaMag > 12 then
currentSpeed = currentSpeed * (20/velocityDeltaMag)
if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
end
end
end
local success, gamepadCameraSensitivity = pcall(function() return GameSettings.GamepadCameraSensitivity end)
finalConstant = success and (gamepadCameraSensitivity * currentSpeed) or currentSpeed
lastVelocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
end
lastThumbstickPos = gamepadPan
lastThumbstickRotate = currentTime
return Vector2_new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * ySensitivity * GameSettings:GetCameraYInvertValue())
end
return ZERO_VECTOR2
end
local InputBeganConn, InputChangedConn, InputEndedConn, WindowUnfocusConn, MenuOpenedConn, ShiftLockToggleConn, GamepadConnectedConn, GamepadDisconnectedConn, TouchActivateConn = nil, nil, nil, nil, nil, nil, nil, nil, nil
function this:DisconnectInputEvents()
if InputBeganConn then
InputBeganConn:disconnect()
InputBeganConn = nil
end
if InputChangedConn then
InputChangedConn:disconnect()
InputChangedConn = nil
end
if InputEndedConn then
InputEndedConn:disconnect()
InputEndedConn = nil
end
if WindowUnfocusConn then
WindowUnfocusConn:disconnect()
WindowUnfocusConn = nil
end
if MenuOpenedConn then
MenuOpenedConn:disconnect()
MenuOpenedConn = nil
end
if ShiftLockToggleConn then
ShiftLockToggleConn:disconnect()
ShiftLockToggleConn = nil
end
if GamepadConnectedConn then
GamepadConnectedConn:disconnect()
GamepadConnectedConn = nil
end
if GamepadDisconnectedConn then
GamepadDisconnectedConn:disconnect()
GamepadDisconnectedConn = nil
end
if subjectStateChangedConn then
subjectStateChangedConn:disconnect()
subjectStateChangedConn = nil
end
if workspaceChangedConn then
workspaceChangedConn:disconnect()
workspaceChangedConn = nil
end
if TouchActivateConn then
TouchActivateConn:disconnect()
TouchActivateConn = nil
end
this.TurningLeft = false
this.TurningRight = false
this.LastCameraTransform = nil
self.LastSubjectCFrame = nil
this.UserPanningTheCamera = false
this.RotateInput = Vector2.new()
this.GamepadPanningCamera = Vector2.new(0,0)
-- Reset input states
startPos = nil
lastPos = nil
panBeginLook = nil
isRightMouseDown = false
isMiddleMouseDown = false
fingerTouches = {}
NumUnsunkTouches = 0
StartingDiff = nil
pinchBeginZoom = nil
-- Unlock mouse for example if right mouse button was being held down
if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
function this:ResetInputStates()
isRightMouseDown = false
isMiddleMouseDown = false
this.TurningRight = false
this.TurningLeft = false
OnMousePanButtonReleased() -- this function doesn't seem to actually need parameters
if UserInputService.TouchEnabled then
--[[menu opening was causing serious touch issues
this should disable all active touch events if
they're active when menu opens.]]
for inputObject, value in pairs(fingerTouches) do
fingerTouches[inputObject] = nil
end
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
StartingDiff = nil
pinchBeginZoom = nil
NumUnsunkTouches = 0
end
end
function this.getGamepadPan(name, state, input)
if state == Enum.UserInputState.Cancel then
this.GamepadPanningCamera = ZERO_VECTOR2
return
end
if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
if inputVector.magnitude > THUMBSTICK_DEADZONE then
this.GamepadPanningCamera = Vector2_new(input.Position.X, -input.Position.Y)
else
this.GamepadPanningCamera = ZERO_VECTOR2
end
end
end
function this.doGamepadZoom(name, state, input)
if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonR3 and state == Enum.UserInputState.Begin then
if this.ZoomEnabled then
if this:GetCameraZoom() > 0.5 then
this:ZoomCamera(0)
else
this:ZoomCamera(10)
end
end
end
end
function this:BindGamepadInputActions()
ContextActionService:BindAction("RootCamGamepadPan", this.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
ContextActionService:BindAction("RootCamGamepadZoom", this.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
end
function this:ConnectInputEvents()
InputBeganConn = UserInputService.InputBegan:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchBegan(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
OnMouse2Down(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
OnMouse3Down(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
OnKeyDown(input, processed)
end
end)
InputChangedConn = UserInputService.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseMovement then
OnMouseMoved(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseWheel then
OnMouseWheel(input, processed)
end
end)
InputEndedConn = UserInputService.InputEnded:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
OnMouse2Up(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
OnMouse3Up(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
OnKeyUp(input, processed)
end
end)
WindowUnfocusConn = UserInputService.WindowFocusReleased:connect(onWindowFocusReleased)
MenuOpenedConn = GuiService.MenuOpened:connect(function()
this:ResetInputStates()
end)
workspaceChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
if workspace.CurrentCamera then
onCurrentCameraChanged()
end
ShiftLockToggleConn = ShiftLockController.OnShiftLockToggled.Event:connect(function()
this:UpdateMouseBehavior()
end)
this.RotateInput = Vector2.new()
this.activeGamepad = nil
local function assignActivateGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
if #connectedGamepads > 0 then
for i = 1, #connectedGamepads do
if this.activeGamepad == nil then
this.activeGamepad = connectedGamepads[i]
elseif connectedGamepads[i].Value < this.activeGamepad.Value then
this.activeGamepad = connectedGamepads[i]
end
end
end
if this.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
this.activeGamepad = Enum.UserInputType.Gamepad1
end
end
GamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if this.activeGamepad ~= gamepadEnum then return end
this.activeGamepad = nil
assignActivateGamepad()
end)
GamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
if this.activeGamepad == nil then
assignActivateGamepad()
end
end)
self:BindGamepadInputActions()
assignActivateGamepad()
-- set mouse behavior
self:UpdateMouseBehavior()
end
--Process tweens related to tap-to-recenter and double-tap-to-zoom
--Needs to be called from specific cameras on each update
function this:ProcessTweens()
for name, tween in pairs(tweens) do
local alpha = math.min(1.0, (tick() - tween.start)/tween.duration)
tween.to = tween.func(tween.from, tween.to, alpha)
if math.abs(1 - alpha) < 0.0001 then
tweens[name] = nil
end
end
end
function this:SetEnabled(newState)
if newState ~= self.Enabled then
self.Enabled = newState
if self.Enabled then
self:ConnectInputEvents()
self.cframe = workspace.CurrentCamera.CFrame
else
self:DisconnectInputEvents()
end
end
end
local function OnPlayerAdded(player)
player.Changed:connect(function(prop)
if this.Enabled then
if prop == "CameraMode" or prop == "CameraMaxZoomDistance" or prop == "CameraMinZoomDistance" then
this:ZoomCameraFixedBy(0)
end
end
end)
local function OnCharacterAdded(newCharacter)
local humanoid = findPlayerHumanoid(player)
local start = tick()
while tick() - start < 0.3 and (humanoid == nil or humanoid.Torso == nil) do
wait()
humanoid = findPlayerHumanoid(player)
end
if humanoid and humanoid.Torso and player.Character == newCharacter then
local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0, math.sin(math.rad(DEFAULT_CAMERA_ANGLE)), 0)).unit
local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
if not IsFinite(horizontalShift) then
horizontalShift = 0
end
if not IsFinite(vertShift) then
vertShift = 0
end
this.RotateInput = Vector2.new(horizontalShift, vertShift)
-- reset old camera info so follow cam doesn't rotate us
this.LastCameraTransform = nil
end
-- Need to wait for camera cframe to update before we zoom in
-- Not waiting will force camera to original cframe
wait()
this:ZoomCamera(this.DefaultZoom)
end
player.CharacterAdded:connect(function(character)
if this.Enabled or SetCameraOnSpawn then
OnCharacterAdded(character)
SetCameraOnSpawn = false
end
end)
if player.Character then
spawn(function() OnCharacterAdded(player.Character) end)
end
end
if PlayersService.LocalPlayer then
OnPlayerAdded(PlayersService.LocalPlayer)
end
PlayersService.ChildAdded:connect(function(child)
if child and PlayersService.LocalPlayer == child then
OnPlayerAdded(PlayersService.LocalPlayer)
end
end)
local function OnGameLoaded()
hasGameLoaded = true
end
spawn(function()
if game:IsLoaded() then
OnGameLoaded()
else
game.Loaded:wait()
OnGameLoaded()
end
end)
local function OnDynamicThumbstickEnabled()
if UserInputService.TouchEnabled then
isDynamicThumbstickEnabled = true
end
end
local function OnDynamicThumbstickDisabled()
isDynamicThumbstickEnabled = false
end
local function OnGameSettingsTouchMovementModeChanged()
if LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
if GameSettings.TouchMovementMode.Name == "DynamicThumbstick" then
OnDynamicThumbstickEnabled()
else
OnDynamicThumbstickDisabled()
end
end
end
local function OnDevTouchMovementModeChanged()
if LocalPlayer.DevTouchMovementMode.Name == "DynamicThumbstick" then
OnDynamicThumbstickEnabled()
else
OnGameSettingsTouchMovementModeChanged()
end
end
if PlayersService.LocalPlayer then
PlayersService.LocalPlayer.Changed:Connect(function(prop)
if prop == "DevTouchMovementMode" then
OnDevTouchMovementModeChanged()
end
end)
OnDevTouchMovementModeChanged()
end
GameSettings.Changed:Connect(function(prop)
if prop == "TouchMovementMode" then
OnGameSettingsTouchMovementModeChanged()
end
end)
OnGameSettingsTouchMovementModeChanged()
GameSettings:SetCameraYInvertVisible()
pcall(function() GameSettings:SetGamepadCameraSensitivityVisible() end)
return this
end
return CreateCamera