1042 lines
29 KiB
Lua
1042 lines
29 KiB
Lua
local Players = game:GetService("Players")
|
|
local UserInputService = game:GetService("UserInputService")
|
|
local StarterGui = game:GetService("StarterGui")
|
|
local GuiService = game:GetService("GuiService")
|
|
local ContextActionService = game:GetService("ContextActionService")
|
|
local TeleportService = game:GetService("TeleportService")
|
|
local Debris = game:GetService("Debris")
|
|
|
|
local LocalPlayer = Players.LocalPlayer
|
|
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
|
|
local GameSettings = UserSettings():GetService("UserGameSettings")
|
|
|
|
local math_abs = math.abs
|
|
local math_asin = math.asin
|
|
local math_atan2 = math.atan2
|
|
local math_floor = math.floor
|
|
local math_min = math.min
|
|
local math_max = math.max
|
|
local math_pi = math.pi
|
|
local math_rad = math.rad
|
|
local Vector2_new = Vector2.new
|
|
local Vector3_new = Vector3.new
|
|
local CFrame_Angles = CFrame.Angles
|
|
local CFrame_new = CFrame.new
|
|
|
|
local MIN_Y = math_rad(-80)
|
|
local MAX_Y = math_rad(80)
|
|
|
|
local ZERO_VECTOR2 = Vector2_new()
|
|
local ZERO_VECTOR3 = Vector3_new()
|
|
local UP_VECTOR = Vector3_new(0, 1, 0)
|
|
local XZ_VECTOR = Vector3_new(1, 0, 1)
|
|
|
|
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 THUMBSTICK_DEADZONE = 0.2
|
|
local DEADZONE = 0.1
|
|
local ZOOM_FACTOR = 0.25
|
|
|
|
local humanoid
|
|
|
|
local function findPlayerHumanoid(player)
|
|
local character = player and player.Character
|
|
if character then
|
|
if not (humanoid and humanoid.Parent == character) then
|
|
humanoid = character:FindFirstChildOfClass("Humanoid")
|
|
end
|
|
end
|
|
return humanoid
|
|
end
|
|
|
|
local function clamp(low, high, num)
|
|
return (num > high and high or num < low and low or num)
|
|
end
|
|
|
|
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 function SCurveTranform(t)
|
|
t = clamp(-1,1,t)
|
|
if t >= 0 then
|
|
return (.35*t) / (.35 - t + 1)
|
|
end
|
|
return -((.8*-t) / (.8 + t + 1))
|
|
end
|
|
|
|
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
|
|
|
|
-- Reset the camera look vector when the camera is enabled for the first time
|
|
local SetCameraOnSpawn = true
|
|
local this = {}
|
|
|
|
local isFirstPerson = false
|
|
local isRightMouseDown = false
|
|
local isMiddleMouseDown = false
|
|
|
|
this.Enabled = false
|
|
this.RotateInput = ZERO_VECTOR2
|
|
this.DefaultZoom = 10
|
|
this.activeGamepad = nil
|
|
this.PartSubjectHack = nil
|
|
|
|
function this:GetHumanoid()
|
|
local player = Players.LocalPlayer
|
|
return findPlayerHumanoid(player)
|
|
end
|
|
|
|
function this:GetHumanoidRootPart()
|
|
local humanoid = this:GetHumanoid()
|
|
return humanoid and humanoid.Torso
|
|
end
|
|
|
|
function this:GetSubjectPosition()
|
|
local camera = workspace.CurrentCamera
|
|
local result = camera.Focus.p
|
|
|
|
local cameraSubject = camera and camera.CameraSubject
|
|
if cameraSubject then
|
|
if cameraSubject:IsA("Humanoid") then
|
|
local char = cameraSubject.Parent
|
|
if char then
|
|
local head = char:FindFirstChild("Head")
|
|
if head and head:IsA("BasePart") then
|
|
result = head.Position
|
|
end
|
|
end
|
|
if this.PartSubjectHack then
|
|
this:ZoomCamera(this.PartSubjectHack)
|
|
this.PartSubjectHack = nil
|
|
this:UpdateMouseBehavior()
|
|
end
|
|
elseif cameraSubject:IsA("BasePart") then
|
|
result = cameraSubject.Position
|
|
if not this.PartSubjectHack then
|
|
this.PartSubjectHack = this:GetCameraZoom()
|
|
this:ZoomCamera(10)
|
|
this:UpdateMouseBehavior()
|
|
end
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function this:GetCameraLook()
|
|
return workspace.CurrentCamera and workspace.CurrentCamera.CFrame.lookVector or Vector3.new(0,0,1)
|
|
end
|
|
|
|
function this:GetCameraZoom()
|
|
if this.currentZoom == nil then
|
|
local player = Players.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.CFrame.p - camera.Focus.p).Magnitude
|
|
end
|
|
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
|
|
|
|
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)
|
|
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
|
|
|
|
function this:IsInFirstPerson()
|
|
return isFirstPerson
|
|
end
|
|
|
|
function this:UpdateMouseBehavior()
|
|
if isFirstPerson or this.PartSubjectHack then
|
|
GameSettings.RotationType = Enum.RotationType.CameraRelative
|
|
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
|
|
else
|
|
GameSettings.RotationType = Enum.RotationType.MovementRelative
|
|
if isRightMouseDown or isMiddleMouseDown then
|
|
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
|
|
else
|
|
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
|
|
end
|
|
end
|
|
end
|
|
|
|
function this:PlayTick()
|
|
local now = tick()
|
|
local lastTickSound = this.LastTickSound
|
|
if not lastTickSound then
|
|
lastTickSound = 0
|
|
end
|
|
|
|
if (now - lastTickSound) > .03 then
|
|
local s = Instance.new("Sound")
|
|
s.SoundId = "rbxasset://sounds/switch3.wav"
|
|
s.Parent = script
|
|
s:Play()
|
|
Debris:AddItem(s,1)
|
|
this.LastTickSound = now
|
|
end
|
|
end
|
|
|
|
function this:ZoomCamera(desiredZoom)
|
|
this.currentZoom = clamp(0.25, 400, desiredZoom)
|
|
isFirstPerson = self:GetCameraZoom() < 1.5
|
|
|
|
-- set mouse behavior
|
|
self:UpdateMouseBehavior()
|
|
return self:GetCameraZoom()
|
|
end
|
|
|
|
function this:ZoomCameraBy(input)
|
|
if TeleportService:GetTeleportSetting("FPSCapTo30") then
|
|
input = input * 1.5
|
|
end
|
|
|
|
local zoom = this:GetCameraActualZoom()
|
|
if zoom then
|
|
if input > 0 then
|
|
zoom = math.max( 1, zoom / (1 + ZOOM_FACTOR*input))
|
|
elseif input < 0 then
|
|
zoom = math.min(5000, zoom * (1 - ZOOM_FACTOR*input))
|
|
end
|
|
self:ZoomCamera(zoom)
|
|
end
|
|
|
|
self:PlayTick()
|
|
return self:GetCameraZoom()
|
|
end
|
|
|
|
function this:ZoomCameraFixedBy(zoomIncrement)
|
|
return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
|
|
end
|
|
|
|
------------------------
|
|
---- Input Events ----
|
|
------------------------
|
|
|
|
do
|
|
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
|
|
|
|
local dynamicThumbstickFrame = nil
|
|
local flaggedDynamic = {}
|
|
|
|
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
|
|
|
|
this.ZoomEnabled = true
|
|
this.PanEnabled = true
|
|
this.KeyPanEnabled = true
|
|
|
|
local function inputIsDynamic(input)
|
|
if flaggedDynamic[input] ~= nil then
|
|
return flaggedDynamic[input]
|
|
end
|
|
|
|
if GameSettings.TouchMovementMode ~= Enum.TouchMovementMode.DynamicThumbstick then
|
|
return false
|
|
end
|
|
|
|
local df = getDynamicThumbstickFrame()
|
|
if not df then return end
|
|
|
|
local pos = input.Position
|
|
local p0 = df.AbsolutePosition
|
|
local p1 = p0 + df.AbsoluteSize
|
|
|
|
if p0.X <= pos.X and p0.Y <= pos.Y then
|
|
if pos.X <= p1.X and pos.Y <= p1.Y then
|
|
flaggedDynamic[input] = true
|
|
return true
|
|
end
|
|
end
|
|
|
|
flaggedDynamic[input] = false
|
|
return false
|
|
end
|
|
|
|
local function OnTouchBegan(input, processed)
|
|
if not inputIsDynamic(input) 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 inputIsDynamic(input) then
|
|
return
|
|
end
|
|
|
|
if fingerTouches[input] == nil then
|
|
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)
|
|
this:PlayTick()
|
|
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,0.23,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 OnTouchEnded(input, processed)
|
|
if fingerTouches[input] == false then
|
|
if NumUnsunkTouches == 1 then
|
|
panBeginLook = nil
|
|
startPos = nil
|
|
lastPos = nil
|
|
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
|
|
flaggedDynamic[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)
|
|
|
|
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.PartSubjectHack) and this.PanEnabled then
|
|
local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
|
|
this.RotateInput = this.RotateInput + desiredXYVector
|
|
end
|
|
end
|
|
|
|
local function OnMouseWheel(input, processed)
|
|
if not processed then
|
|
if this.ZoomEnabled then
|
|
this:ZoomCameraBy(clamp(-1, 1, input.Position.Z))
|
|
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 processed then return end
|
|
if this.ZoomEnabled then
|
|
if input.KeyCode == Enum.KeyCode.I then
|
|
this:ZoomCameraBy(1)
|
|
elseif input.KeyCode == Enum.KeyCode.O then
|
|
this:ZoomCameraBy(-1)
|
|
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
|
|
this:PlayTick()
|
|
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
|
|
this:PlayTick()
|
|
elseif input.KeyCode == Enum.KeyCode.PageUp then
|
|
this.RotateInput = this.RotateInput + Vector2.new(0,math_pi/12)
|
|
this.LastCameraTransform = nil
|
|
this:PlayTick()
|
|
elseif input.KeyCode == Enum.KeyCode.PageDown then
|
|
this.RotateInput = this.RotateInput + Vector2.new(0,-math_pi/12)
|
|
this.LastCameraTransform = nil
|
|
this:PlayTick()
|
|
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 lastThumbstickRotate = nil
|
|
local numOfSeconds = 0.7
|
|
local currentSpeed = 0
|
|
local maxSpeed = 6
|
|
local lastThumbstickPos = Vector2.new(0,0)
|
|
local ySensitivity = 0.65
|
|
local lastVelocity = nil
|
|
|
|
function this:UpdateGamepad()
|
|
local gamepadPan = this.GamepadPanningCamera
|
|
if gamepadPan 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
|
|
local elapsed = (currentTime - lastThumbstickRotate) * 10
|
|
currentSpeed = currentSpeed + (maxSpeed * ((elapsed*elapsed)/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
|
|
|
|
local gamepadCameraSensitivity = GameSettings.GamepadCameraSensitivity
|
|
finalConstant = (gamepadCameraSensitivity * 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 InputEvents = {}
|
|
|
|
function this:DisconnectInputEvents()
|
|
-- Disconnect all input events.
|
|
while true do
|
|
local signalName = next(InputEvents)
|
|
if signalName then
|
|
InputEvents[signalName]:Disconnect()
|
|
InputEvents[signalName] = nil
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
this.TurningLeft = false
|
|
this.TurningRight = false
|
|
this.LastCameraTransform = nil
|
|
this.UserPanningTheCamera = false
|
|
this.RotateInput = ZERO_VECTOR2
|
|
this.GamepadPanningCamera = ZERO_VECTOR2
|
|
|
|
-- 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
|
|
|
|
local function resetInputStates()
|
|
isRightMouseDown = false
|
|
isMiddleMouseDown = 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
|
|
|
|
local function getGamepadPan(name, state, input)
|
|
if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
|
|
|
|
if state == Enum.UserInputState.Cancel then
|
|
this.GamepadPanningCamera = ZERO_VECTOR2
|
|
return
|
|
end
|
|
|
|
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
|
|
|
|
local function 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
|
|
|
|
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
|
|
|
|
local function onInputBegan(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
|
|
|
|
local function onInputChanged(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
|
|
|
|
local function onInputEnded(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
|
|
|
|
local inputPassCmds =
|
|
{
|
|
ZoomIn = Enum.KeyCode.I;
|
|
ZoomOut = Enum.KeyCode.O;
|
|
RotateUp = Enum.KeyCode.PageUp;
|
|
RotateDown = Enum.KeyCode.PageDown;
|
|
}
|
|
|
|
local function onInputPassed(command)
|
|
local passKey = inputPassCmds[command]
|
|
if passKey then
|
|
OnKeyDown({KeyCode = passKey}, false)
|
|
end
|
|
end
|
|
|
|
local function onGamepadConnected(gamepadEnum)
|
|
if this.activeGamepad == nil then
|
|
assignActivateGamepad()
|
|
end
|
|
end
|
|
|
|
local function onGamepadDisconnected(gamepadEnum)
|
|
if this.activeGamepad ~= gamepadEnum then return end
|
|
this.activeGamepad = nil
|
|
assignActivateGamepad()
|
|
end
|
|
|
|
function this:ConnectInputEvents()
|
|
local player = Players.LocalPlayer
|
|
local playerScripts = player:WaitForChild("PlayerScripts")
|
|
local passCameraEvent = playerScripts:WaitForChild("PassCameraEvent")
|
|
|
|
this.RotateInput = ZERO_VECTOR2
|
|
this.activeGamepad = nil
|
|
|
|
InputEvents =
|
|
{
|
|
InputBegan = UserInputService.InputBegan:Connect(onInputBegan);
|
|
InputChanged = UserInputService.InputChanged:Connect(onInputChanged);
|
|
InputEnded = UserInputService.InputEnded:Connect(onInputEnded);
|
|
MenuOpened = GuiService.MenuOpened:Connect(resetInputStates);
|
|
MenuOpenedConn = GuiService.MenuOpened:Connect(resetInputStates);
|
|
GamepadConnected = UserInputService.GamepadConnected:Connect(onGamepadConnected);
|
|
GamepadDisconnected = UserInputService.GamepadDisconnected:Connect(onGamepadDisconnected);
|
|
InputPassed = passCameraEvent.Event:Connect(onInputPassed);
|
|
}
|
|
|
|
ContextActionService:BindAction("RootCamGamepadPan", getGamepadPan, false, Enum.KeyCode.Thumbstick2)
|
|
ContextActionService:BindAction("RootCamGamepadZoom", doGamepadZoom, false, Enum.KeyCode.ButtonR3)
|
|
|
|
assignActivateGamepad()
|
|
|
|
-- set mouse behavior
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
|
|
function this:SetEnabled(newState)
|
|
if newState ~= self.Enabled then
|
|
self.Enabled = newState
|
|
if self.Enabled then
|
|
self:ConnectInputEvents()
|
|
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,0.23,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 Players.LocalPlayer then
|
|
OnPlayerAdded(Players.LocalPlayer)
|
|
end
|
|
|
|
Players.ChildAdded:Connect(function (child)
|
|
if child and Players.LocalPlayer == child then
|
|
OnPlayerAdded(Players.LocalPlayer)
|
|
end
|
|
end)
|
|
|
|
end
|
|
|
|
------------------------
|
|
---- Main Updater ----
|
|
------------------------
|
|
|
|
do
|
|
local tweenAcceleration = math_rad(220)
|
|
local tweenSpeed = math_rad(0)
|
|
local tweenMaxSpeed = math_rad(250)
|
|
local timeBeforeAutoRotate = 2
|
|
|
|
local lastUpdate = tick()
|
|
this.LastUserPanCamera = lastUpdate
|
|
|
|
|
|
function this:Update()
|
|
local now = tick()
|
|
local timeDelta = (now - lastUpdate)
|
|
|
|
local userPanningTheCamera = (self.UserPanningTheCamera == true)
|
|
local camera = workspace.CurrentCamera
|
|
local humanoid = self:GetHumanoid()
|
|
local cameraSubject = camera and camera.CameraSubject
|
|
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
|
|
local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
|
|
|
|
if isInVehicle and cameraSubject.Occupant == humanoid then
|
|
cameraSubject = humanoid
|
|
camera.CameraSubject = humanoid
|
|
isInVehicle = false
|
|
end
|
|
|
|
if lastUpdate == nil or (now - lastUpdate) > 1 then
|
|
self.LastCameraTransform = nil
|
|
end
|
|
|
|
if lastUpdate then
|
|
local gamepadRotation = self:UpdateGamepad()
|
|
|
|
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
|
|
local delta = math_min(0.1, now - lastUpdate)
|
|
|
|
if gamepadRotation ~= ZERO_VECTOR2 then
|
|
userPanningTheCamera = true
|
|
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
|
end
|
|
|
|
local angle = 0
|
|
if not (isInVehicle or isOnASkateboard) then
|
|
angle = angle + (self.TurningLeft and -120 or 0)
|
|
angle = angle + (self.TurningRight and 120 or 0)
|
|
end
|
|
|
|
if angle ~= 0 then
|
|
self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
|
|
userPanningTheCamera = true
|
|
end
|
|
end
|
|
|
|
-- Reset tween speed if user is panning
|
|
if userPanningTheCamera then
|
|
tweenSpeed = 0
|
|
self.LastUserPanCamera = now
|
|
end
|
|
|
|
local userRecentlyPannedCamera = now - self.LastUserPanCamera < timeBeforeAutoRotate
|
|
local subjectPosition = self:GetSubjectPosition()
|
|
|
|
if subjectPosition and camera then
|
|
local zoom = self:GetCameraZoom()
|
|
if zoom < 0.25 then
|
|
zoom = 0.25
|
|
end
|
|
|
|
if TeleportService:GetTeleportSetting("FollowCamera") then
|
|
if self.LastCameraTransform and not self:IsInFirstPerson() then
|
|
local lastVec = -(self.LastCameraTransform.p - subjectPosition)
|
|
local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
|
|
-- Check for NaNs
|
|
if IsFinite(y) and math.abs(y) > 0.0001 then
|
|
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
camera.Focus = CFrame_new(subjectPosition)
|
|
|
|
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
|
self.RotateInput = ZERO_VECTOR2
|
|
|
|
if self.LastZoom ~= zoom then
|
|
self.LastZoom = zoom
|
|
|
|
if camera.CameraSubject and camera.CameraSubject:IsA("Humanoid") then
|
|
-- Flatten the lookVector
|
|
newLookVector = (newLookVector * XZ_VECTOR).Unit
|
|
|
|
-- Apply upwards tilt
|
|
local upY = -math_min(6, zoom/40)
|
|
newLookVector = (newLookVector + (UP_VECTOR * upY)).Unit
|
|
end
|
|
end
|
|
|
|
local newCF = CFrame_new(subjectPosition - (zoom * newLookVector), subjectPosition)
|
|
camera.CFrame = camera.CFrame:Lerp(newCF,.8)
|
|
self.LastCameraTransform = camera.CFrame
|
|
end
|
|
|
|
lastUpdate = now
|
|
end
|
|
|
|
GameSettings:SetCameraYInvertVisible()
|
|
GameSettings:SetGamepadCameraSensitivityVisible()
|
|
end
|
|
|
|
return this |