Clients/Client2018/content/scripts/PlayerScripts/StarterPlayerScripts_NewStr.../RobloxPlayerScript/CameraScript/BaseCamera.lua

1505 lines
49 KiB
Lua

--[[
BaseCamera - Abstract base class for camera control modules
2018 Camera Update - AllYourBlox
--]]
--[[ Local Constants ]]--
local UNIT_Z = Vector3.new(0,0,1)
local X1_Y0_Z1 = Vector3.new(1,0,1) --Note: not a unit vector, used for projecting onto XZ plane
local THUMBSTICK_DEADZONE = 0.2
local DEFAULT_DISTANCE = 12.5 -- Studs
local PORTRAIT_DEFAULT_DISTANCE = 25 -- Studs
local FIRST_PERSON_DISTANCE_THRESHOLD = 1.0 -- Below this value, snap into first person
-- Note: DotProduct check in CoordinateFrame::lookAt() prevents using values within about
-- 8.11 degrees of the +/- Y axis, that's why these limits are currently 80 degrees
local MIN_Y = math.rad(-80)
local MAX_Y = math.rad(80)
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( 0.002 * math.pi, 0.0015 * math.pi)
local MOUSE_SENSITIVITY = Vector2.new( 0.002 * math.pi, 0.0015 * math.pi )
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)
local Util = require(script.Parent:WaitForChild("CameraUtils"))
--[[ Roblox Services ]]--
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 VRService = game:GetService("VRService")
local UserGameSettings = UserSettings():GetService("UserGameSettings")
--[[ The Module ]]--
local BaseCamera = {}
BaseCamera.__index = BaseCamera
function BaseCamera.new()
local self = setmetatable({}, BaseCamera)
-- So that derived classes have access to this
self.FIRST_PERSON_DISTANCE_THRESHOLD = FIRST_PERSON_DISTANCE_THRESHOLD
self.cameraType = nil
self.cameraMovementMode = nil
local player = Players.LocalPlayer
self.lastCameraTransform = nil
self.rotateInput = ZERO_VECTOR2
self.userPanningCamera = false
self.lastUserPanCamera = tick()
self.humanoidRootPart = nil
self.humanoidCache = {}
-- Subject and position on last update call
self.lastSubject = nil
self.lastSubjectPosition = Vector3.new(0,5,0)
-- These subject distance members refer to the nominal camera-to-subject follow distance that the camera
-- is trying to maintain, not the actual measured value.
-- The default is updated when screen orientation or the min/max distances change,
-- to be sure the default is always in range and appropriate for the orientation.
self.defaultSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, DEFAULT_DISTANCE)
self.currentSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, DEFAULT_DISTANCE)
self.inFirstPerson = false
self.inMouseLockedMode = false
self.portraitMode = false
self.enabled = false
-- Input Event Connections
self.inputBeganConn = nil
self.inputChangedConn = nil
self.inputEndedConn = nil
self.startPos = nil
self.lastPos = nil
self.panBeginLook = nil
self.panEnabled = true
self.keyPanEnabled = true
self.distanceChangeEnabled = true
self.PlayerGui = nil
self.cameraChangedConn = nil
self.viewportSizeChangedConn = nil
-- VR Support
self.shouldUseVRRotation = false
self.VRRotationIntensityAvailable = false
self.lastVRRotationIntensityCheckTime = 0
self.lastVRRotationTime = 0
self.vrRotateKeyCooldown = {}
self.cameraTranslationConstraints = Vector3.new(1, 1, 1)
self.humanoidJumpOrigin = nil
self.trackingHumanoid = nil
self.cameraFrozen = false
self.headHeightR15 = R15_HEAD_OFFSET
self.heightScaleChangedConn = nil
self.subjectStateChangedConn = nil
self.humanoidChildAddedConn = nil
self.humanoidChildRemovedConn = nil
-- Gamepad support
self.activeGamepad = nil
self.gamepadPanningCamera = false
self.lastThumbstickRotate = nil
self.numOfSeconds = 0.7
self.currentSpeed = 0
self.maxSpeed = 6
self.vrMaxSpeed = 4
self.lastThumbstickPos = Vector2.new(0,0)
self.ySensitivity = 0.65
self.lastVelocity = nil
self.gamepadConnectedConn = nil
self.gamepadDisconnectedConn = nil
self.currentZoomSpeed = 1.0
self.L3ButtonDown = false
self.dpadLeftDown = false
self.dpadRightDown = false
-- Touch input support
self.isDynamicThumbstickEnabled = false
self.fingerTouches = {}
self.numUnsunkTouches = 0
self.inputStartPositions = {}
self.inputStartTimes = {}
self.startingDiff = nil
self.pinchBeginZoom = nil
self.userPanningTheCamera = false
self.touchActivateConn = nil
-- Mouse locked formerly known as shift lock mode
self.mouseLockOffset = ZERO_VECTOR3
-- [[ NOTICE ]] --
-- Initialization things used to always execute at game load time, but now these camera modules are instantiated
-- when needed, so the code here may run well after the start of the game
if player.Character then
self:OnCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(function(char)
self:OnCharacterAdded(char)
end)
if self.cameraChangedConn then self.cameraChangedConn:Disconnect() end
self.cameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
self:OnCurrentCameraChanged()
end)
if self.playerCameraModeChangeConn then self.playerCameraModeChangeConn:Disconnect() end
self.playerCameraModeChangeConn = player:GetPropertyChangedSignal("CameraMode"):Connect(function()
self:OnPlayerCameraPropertyChange()
end)
if self.minDistanceChangeConn then self.minDistanceChangeConn:Disconnect() end
self.minDistanceChangeConn = player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(function()
self:OnPlayerCameraPropertyChange()
end)
if self.maxDistanceChangeConn then self.maxDistanceChangeConn:Disconnect() end
self.maxDistanceChangeConn = player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(function()
self:OnPlayerCameraPropertyChange()
end)
if self.playerDevTouchMoveModeChangeConn then self.playerDevTouchMoveModeChangeConn:Disconnect() end
self.playerDevTouchMoveModeChangeConn = player:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
self:OnDevTouchMovementModeChanged()
end)
self:OnDevTouchMovementModeChanged() -- Init
if self.gameSettingsTouchMoveMoveChangeConn then self.gameSettingsTouchMoveMoveChangeConn:Disconnect() end
self.gameSettingsTouchMoveMoveChangeConn = UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
self:OnGameSettingsTouchMovementModeChanged()
end)
self:OnGameSettingsTouchMovementModeChanged() -- Init
UserGameSettings:SetCameraYInvertVisible()
UserGameSettings:SetGamepadCameraSensitivityVisible()
self.hasGameLoaded = game:IsLoaded()
if not self.hasGameLoaded then
self.gameLoadedConn = game.Loaded:Connect(function()
self.hasGameLoaded = true
self.gameLoadedConn:Disconnect()
self.gameLoadedConn = nil
end)
end
return self
end
function BaseCamera:GetModuleName()
return "BaseCamera"
end
function BaseCamera:OnCharacterAdded(char)
if UserInputService.TouchEnabled then
self.PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
if self.PlayerGui then
local screenGui = Instance.new("ScreenGui")
screenGui.Parent = self.PlayerGui
end
for _, child in ipairs(char:GetChildren()) do
if child:IsA("Tool") then
self.isAToolEquipped = true
end
end
char.ChildAdded:Connect(function(child)
if child:IsA("Tool") then
self.isAToolEquipped = true
end
end)
char.ChildRemoved:Connect(function(child)
if child:IsA("Tool") then
self.isAToolEquipped = false
end
end)
end
end
function BaseCamera:GetHumanoidRootPart()
if not self.humanoidRootPart then
local player = Players.LocalPlayer
if player.Character then
local humanoid = player.Character:FindFirstChildOfClass("Humanoid")
if humanoid then
self.humanoidRootPart = humanoid.RootPart
end
end
end
return self.humanoidRootPart
end
function BaseCamera:GetBodyPartToFollow(humanoid, isDead)
-- If the humanoid is dead, prefer the head part if one still exists as a sibling of the humanoid
if humanoid:GetState() == Enum.HumanoidStateType.Dead then
local character = humanoid.Parent
if character and character:IsA("Model") then
return character:FindFirstChild("Head") or humanoid.RootPart
end
end
return humanoid.RootPart
end
function BaseCamera:GetSubjectPosition()
local result = self.lastSubjectPosition
local camera = game.Workspace.CurrentCamera
local cameraSubject = camera and camera.CameraSubject
if cameraSubject then
if cameraSubject:IsA("Humanoid") then
local humanoid = cameraSubject
local humanoidIsDead = humanoid:GetState() == Enum.HumanoidStateType.Dead
if VRService.VREnabled and humanoidIsDead and humanoid == self.lastSubject then
result = self.lastSubjectPosition
else
local bodyPartToFollow = humanoid.RootPart
-- If the humanoid is dead, prefer their head part as a follow target, if it exists
if humanoidIsDead then
if humanoid.Parent and humanoid.Parent:IsA("Model") then
bodyPartToFollow = humanoid.Parent:FindFirstChild("Head") or bodyPartToFollow
end
end
if bodyPartToFollow and bodyPartToFollow:IsA("BasePart") then
local heightOffset = humanoid.RigType == Enum.HumanoidRigType.R15 and R15_HEAD_OFFSET or HEAD_OFFSET
if humanoidIsDead then
heightOffset = ZERO_VECTOR3
end
result = bodyPartToFollow.CFrame.p + bodyPartToFollow.CFrame:vectorToWorldSpace(heightOffset + humanoid.CameraOffset)
end
end
elseif cameraSubject:IsA("VehicleSeat") then
local offset = SEAT_OFFSET
if VRService.VREnabled then
offset = VR_SEAT_OFFSET
end
result = cameraSubject.CFrame.p + cameraSubject.CFrame:vectorToWorldSpace(offset)
elseif cameraSubject:IsA("SkateboardPlatform") then
result = cameraSubject.CFrame.p + SEAT_OFFSET
elseif cameraSubject:IsA("BasePart") then
result = cameraSubject.CFrame.p
elseif cameraSubject:IsA("Model") then
if cameraSubject.PrimaryPart then
result = cameraSubject:GetPrimaryPartCFrame().p
else
result = cameraSubject:GetModelCFrame().p
end
end
else
-- cameraSubject is nil
-- Note: Previous RootCamera did not have this else case and let self.lastSubject and self.lastSubjectPosition
-- both get set to nil in the case of cameraSubject being nil. This function now exits here to preserve the
-- last set valid values for these, as nil values are not handled cases
return
end
self.lastSubject = cameraSubject
self.lastSubjectPosition = result
return result
end
function BaseCamera:UpdateDefaultSubjectDistance()
local player = Players.LocalPlayer
if self.portraitMode then
self.defaultSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, PORTRAIT_DEFAULT_DISTANCE)
else
self.defaultSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, DEFAULT_DISTANCE)
end
end
function BaseCamera:OnViewportSizeChanged()
local camera = game.Workspace.CurrentCamera
local size = camera.ViewportSize
self.portraitMode = size.X < size.Y
self:UpdateDefaultSubjectDistance()
end
-- Listener for changes to workspace.CurrentCamera
function BaseCamera:OnCurrentCameraChanged()
if UserInputService.TouchEnabled then
if self.viewportSizeChangedConn then
self.viewportSizeChangedConn:Disconnect()
self.viewportSizeChangedConn = nil
end
local newCamera = game.Workspace.CurrentCamera
if newCamera then
self:OnViewportSizeChanged()
self.viewportSizeChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
self:OnViewportSizeChanged()
end)
end
end
-- VR support additions
if self.cameraSubjectChangedConn then
self.cameraSubjectChangedConn:Disconnect()
self.cameraSubjectChangedConn = nil
end
local camera = game.Workspace.CurrentCamera
if camera then
self.cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
self:OnNewCameraSubject()
end)
self:OnNewCameraSubject()
end
end
function BaseCamera:OnDynamicThumbstickEnabled()
if UserInputService.TouchEnabled then
self.isDynamicThumbstickEnabled = true
end
end
function BaseCamera:OnDynamicThumbstickDisabled()
self.isDynamicThumbstickEnabled = false
end
function BaseCamera:OnGameSettingsTouchMovementModeChanged()
if Players.LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
if UserGameSettings.TouchMovementMode.Name == "DynamicThumbstick" then
self:OnDynamicThumbstickEnabled()
else
self:OnDynamicThumbstickDisabled()
end
end
end
function BaseCamera:OnDevTouchMovementModeChanged()
if Players.LocalPlayer.DevTouchMovementMode.Name == "DynamicThumbstick" then
self:OnDynamicThumbstickEnabled()
else
self:OnGameSettingsTouchMovementModeChanged()
end
end
function BaseCamera:OnPlayerCameraPropertyChange()
-- This call forces re-evaluation of player.CameraMode and clamping to min/max distance which may have changed
self:SetCameraToSubjectDistance(self.currentSubjectDistance)
end
function BaseCamera:GetCameraHeight()
if VRService.VREnabled and not self.inFirstPerson then
return math.sin(VR_ANGLE) * self.currentSubjectDistance
end
return 0
end
function BaseCamera:InputTranslationToCameraAngleChange(translationVector, sensitivity)
local camera = game.Workspace.CurrentCamera
if camera and camera.ViewportSize.X > 0 and camera.ViewportSize.Y > 0 and (camera.ViewportSize.Y > camera.ViewportSize.X) then
-- Screen has portrait orientation, swap X and Y sensitivity
return translationVector * Vector2.new( sensitivity.Y, sensitivity.X)
end
return translationVector * sensitivity
end
function BaseCamera:Enable(enable)
if self.enabled ~= enable then
self.enabled = enable
if self.enabled then
self:ConnectInputEvents()
if Players.LocalPlayer.CameraMode == Enum.CameraMode.LockFirstPerson then
self.currentSubjectDistance = 0.5
if not self.inFirstPerson then
self:EnterFirstPerson()
end
end
else
self:DisconnectInputEvents()
-- Clean up additional event listeners and reset a bunch of properties
self:Cleanup()
end
end
end
function BaseCamera:GetEnabled()
return self.enabled
end
function BaseCamera:OnInputBegan(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchBegan(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
self:OnMouse2Down(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
self:OnMouse3Down(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
self:OnKeyDown(input, processed)
end
end
function BaseCamera:OnInputChanged(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchChanged(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseMovement then
self:OnMouseMoved(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseWheel then
self:OnMouseWheel(input, processed)
end
end
function BaseCamera:OnInputEnded(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchEnded(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
self:OnMouse2Up(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
self:OnMouse3Up(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
self:OnKeyUp(input, processed)
end
end
function BaseCamera:ConnectInputEvents()
self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
self:OnInputBegan(input, processed)
end)
self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
self:OnInputChanged(input, processed)
end)
self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
self:OnInputEnded(input, processed)
end)
self.touchActivateConn = UserInputService.TouchTapInWorld:Connect(function(touchPos, processed)
self:OnTouchTap(touchPos)
end)
self.menuOpenedConn = GuiService.MenuOpened:connect(function()
self:ResetInputStates()
end)
self.gamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if self.activeGamepad ~= gamepadEnum then return end
self.activeGamepad = nil
self:AssignActivateGamepad()
end)
self.gamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
if self.activeGamepad == nil then
self:AssignActivateGamepad()
end
end)
self:BindGamepadInputActions()
self:AssignActivateGamepad()
self:UpdateMouseBehavior()
end
function BaseCamera:AssignActivateGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
if #connectedGamepads > 0 then
for i = 1, #connectedGamepads do
if self.activeGamepad == nil then
self.activeGamepad = connectedGamepads[i]
elseif connectedGamepads[i].Value < self.activeGamepad.Value then
self.activeGamepad = connectedGamepads[i]
end
end
end
if self.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
self.activeGamepad = Enum.UserInputType.Gamepad1
end
end
function BaseCamera:DisconnectInputEvents()
if self.inputBeganConn then
self.inputBeganConn:Disconnect()
self.inputBeganConn = nil
end
if self.inputChangedConn then
self.inputChangedConn:Disconnect()
self.inputChangedConn = nil
end
if self.inputEndedConn then
self.inputEndedConn:Disconnect()
self.inputEndedConn = nil
end
end
function BaseCamera:Cleanup()
if self.menuOpenedConn then
self.menuOpenedConn:Disconnect()
self.menuOpenedConn = nil
end
if self.mouseLockToggleConn then
self.mouseLockToggleConn:Disconnect()
self.mouseLockToggleConn = nil
end
if self.gamepadConnectedConn then
self.gamepadConnectedConn:Disconnect()
self.gamepadConnectedConn = nil
end
if self.gamepadDisconnectedConn then
self.gamepadDisconnectedConn:Disconnect()
self.gamepadDisconnectedConn = nil
end
if self.subjectStateChangedConn then
self.subjectStateChangedConn:Disconnect()
self.subjectStateChangedConn = nil
end
if self.viewportSizeChangedConn then
self.viewportSizeChangedConn:Disconnect()
self.viewportSizeChangedConn = nil
end
if self.touchActivateConn then
self.touchActivateConn:Disconnect()
self.touchActivateConn = nil
end
self.turningLeft = false
self.turningRight = false
self.lastCameraTransform = nil
self.lastSubjectCFrame = nil
self.userPanningTheCamera = false
self.rotateInput = Vector2.new()
self.gamepadPanningCamera = Vector2.new(0,0)
-- Reset input states
self.startPos = nil
self.lastPos = nil
self.panBeginLook = nil
self.isRightMouseDown = false
self.isMiddleMouseDown = false
self.fingerTouches = {}
self.numUnsunkTouches = 0
self.startingDiff = nil
self.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
-- This is called when settings menu is opened
function BaseCamera:ResetInputStates()
self.isRightMouseDown = false
self.isMiddleMouseDown = false
self: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 in pairs(self.fingerTouches) do
self.fingerTouches[inputObject] = nil
end
self.panBeginLook = nil
self.startPos = nil
self.lastPos = nil
self.userPanningTheCamera = false
self.startingDiff = nil
self.pinchBeginZoom = nil
self.numUnsunkTouches = 0
end
end
function BaseCamera:GetGamepadPan(name, state, input)
if input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
-- if self.L3ButtonDown then
-- -- L3 Thumbstick is depressed, right stick controls dolly in/out
-- if (input.Position.Y > THUMBSTICK_DEADZONE) then
-- self.currentZoomSpeed = 0.96
-- elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
-- self.currentZoomSpeed = 1.04
-- else
-- self.currentZoomSpeed = 1.00
-- end
-- else
if state == Enum.UserInputState.Cancel then
self.gamepadPanningCamera = ZERO_VECTOR2
return
end
local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
if inputVector.magnitude > THUMBSTICK_DEADZONE then
self.gamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
else
self.gamepadPanningCamera = ZERO_VECTOR2
end
--end
end
end
function BaseCamera:DoGamepadZoom(name, state, input)
if input.UserInputType == self.activeGamepad then
if input.KeyCode == Enum.KeyCode.ButtonR3 then
if state == Enum.UserInputState.Begin then
if self.distanceChangeEnabled then
if self:GetCameraToSubjectDistance() > 0.5 then
self:SetCameraToSubjectDistance(0)
else
self:SetCameraToSubjectDistance(10)
end
end
end
elseif input.KeyCode == Enum.KeyCode.DPadLeft then
self.dpadLeftDown = (state == Enum.UserInputState.Begin)
elseif input.KeyCode == Enum.KeyCode.DPadRight then
self.dpadRightDown = (state == Enum.UserInputState.Begin)
end
if self.dpadLeftDown then
self.currentZoomSpeed = 1.04
elseif self.dpadRightDown then
self.currentZoomSpeed = 0.96
else
self.currentZoomSpeed = 1.00
end
end
-- elseif input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonL3 then
-- if (state == Enum.UserInputState.Begin) then
-- self.L3ButtonDown = true
-- elseif (state == Enum.UserInputState.End) then
-- self.L3ButtonDown = false
-- self.currentZoomSpeed = 1.00
-- end
-- end
end
function BaseCamera:BindGamepadInputActions()
ContextActionService:BindAction("RootCamGamepadPan", function(name, state, input) self:GetGamepadPan(name, state, input) end, false, Enum.KeyCode.Thumbstick2)
ContextActionService:BindAction("RootCamGamepadZoom", function(name, state, input) self:DoGamepadZoom(name, state, input) end, false, Enum.KeyCode.ButtonR3)
--ContextActionService:BindAction("RootGamepadZoomAlt", function(name, state, input) self:DoGamepadZoom(name, state, input) end, false, Enum.KeyCode.ButtonL3)
ContextActionService:BindAction("RootGamepadZoomOut", function(name, state, input) self:DoGamepadZoom(name, state, input) end, false, Enum.KeyCode.DPadLeft)
ContextActionService:BindAction("RootGamepadZoomIn", function(name, state, input) self:DoGamepadZoom(name, state, input) end, false, Enum.KeyCode.DPadRight)
end
function BaseCamera:OnTouchBegan(input, processed)
local canUseDynamicTouch = self.isDynamicThumbstickEnabled and not processed
if canUseDynamicTouch then
self.fingerTouches[input] = processed
if not processed then
self.inputStartPositions[input] = input.Position
self.inputStartTimes[input] = tick()
self.numUnsunkTouches = self.numUnsunkTouches + 1
end
end
end
function BaseCamera:OnTouchChanged(input, processed)
if self.fingerTouches[input] == nil then
if self.isDynamicThumbstickEnabled then
return
end
self.fingerTouches[input] = processed
if not processed then
self.numUnsunkTouches = self.numUnsunkTouches + 1
end
end
if self.numUnsunkTouches == 1 then
if self.fingerTouches[input] == false then
self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
self.startPos = self.startPos or input.Position
self.lastPos = self.lastPos or self.startPos
self.userPanningTheCamera = true
local delta = input.Position - self.lastPos
delta = Vector2.new(delta.X, delta.Y * UserGameSettings:GetCameraYInvertValue())
if self.panEnabled then
local desiredXYVector = self:InputTranslationToCameraAngleChange(delta, TOUCH_SENSITIVTY)
self.rotateInput = self.rotateInput + desiredXYVector
end
self.lastPos = input.Position
end
else
self.panBeginLook = nil
self.startPos = nil
self.lastPos = nil
self.userPanningTheCamera = false
end
if self.numUnsunkTouches == 2 then
local unsunkTouches = {}
for touch, wasSunk in pairs(self.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 self.startingDiff and self.pinchBeginZoom then
local scale = difference / math.max(0.01, self.startingDiff)
local clampedScale = Util.Clamp(0.1, 10, scale)
if self.distanceChangeEnabled then
self:SetCameraToSubjectDistance(self.pinchBeginZoom / clampedScale)
end
else
self.startingDiff = difference
self.pinchBeginZoom = self:GetCameraToSubjectDistance()
end
end
else
self.startingDiff = nil
self.pinchBeginZoom = nil
end
end
function BaseCamera:CalcLookBehindRotateInput()
if not self.humanoidRootPart or not game.Workspace.CurrentCamera then
return nil
end
local cameraLookVector = game.Workspace.CurrentCamera.CFrame.lookVector
local newDesiredLook = (self.humanoidRootPart.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
local horizontalShift = Util.GetAngleBetweenXZVectors(newDesiredLook, cameraLookVector)
local vertShift = math.asin(cameraLookVector.Y) - math.asin(newDesiredLook.Y)
if not Util.IsFinite(horizontalShift) then
horizontalShift = 0
end
if not Util.IsFinite(vertShift) then
vertShift = 0
end
return Vector2.new(horizontalShift, vertShift)
end
function BaseCamera:OnTouchTap(position)
if self.isDynamicThumbstickEnabled and not self.isAToolEquipped then
if self.lastTapTime and tick() - self.lastTapTime < MAX_TIME_FOR_DOUBLE_TAP then
-- local tween = {
-- from = self:GetCameraToSubjectDistance(),
-- to = self.defaultSubjectDistance,
-- start = tick(),
-- duration = 0.2,
-- func = function(from, to, alpha)
-- self:SetCameraToSubjectDistance(from + (to - from)*alpha)
-- return to
-- end
-- }
-- tweens["Zoom"] = tween
self:SetCameraToSubjectDistance(self.defaultSubjectDistance)
else
if self.humanoidRootPart then
-- TODO: Replace this with proper tween that does not fight with user input
-- this overrides any actual user input with a rotate input amount to get the
-- camera looking from behind the character
self.rotateInput = self:CalcLookBehindRotateInput()
end
-- local humanoid = self:GetHumanoid()
-- if humanoid then
-- local player = Players.LocalPlayer
-- if player and player.Character then
-- if humanoid and humanoid.RootPart then
-- local tween = {
-- from = this.RotateInput,
-- to = calcLookBehindRotateInput(humanoid.Torso),
-- start = tick(),
-- duration = 0.2,
-- func = function(from, to, alpha)
-- to = calcLookBehindRotateInput(humanoid.Torso)
-- if to then
-- this.RotateInput = from + (to - from)*alpha
-- end
-- return to
-- end
-- }
-- tweens["Rotate"] = tween
--
-- -- reset old camera info so follow cam doesn't rotate us
-- this.LastCameraTransform = nil
-- end
-- end
-- end
end
self.lastTapTime = tick()
end
end
function BaseCamera: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 self.inputStartPositions[input] then
local posDelta = (self.inputStartPositions[input] - input.Position).magnitude
if posDelta < MAX_TAP_POS_DELTA then
local timeDelta = self.inputStartTimes[input] - tick()
if timeDelta < MAX_TAP_TIME_DELTA then
return true
end
end
end
return false
end
function BaseCamera:OnTouchEnded(input, processed)
if self.fingerTouches[input] == false then
if self.numUnsunkTouches == 1 then
self.panBeginLook = nil
self.startPos = nil
self.lastPos = nil
self.userPanningTheCamera = false
if self:IsTouchTap(input) then
self:OnTouchTap(input.Position)
end
elseif self.numUnsunkTouches == 2 then
self.startingDiff = nil
self.pinchBeginZoom = nil
end
end
if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
self.numUnsunkTouches = self.numUnsunkTouches - 1
end
self.fingerTouches[input] = nil
self.inputStartPositions[input] = nil
self.inputStartTimes[input] = nil
end
function BaseCamera:OnMouse2Down(input, processed)
if processed then return end
self.isRightMouseDown = true
self:OnMousePanButtonPressed(input, processed)
end
function BaseCamera:OnMouse2Up(input, processed)
self.isRightMouseDown = false
self:OnMousePanButtonReleased(input, processed)
end
function BaseCamera:OnMouse3Down(input, processed)
if processed then return end
self.isMiddleMouseDown = true
self:OnMousePanButtonPressed(input, processed)
end
function BaseCamera:OnMouse3Up(input, processed)
self.isMiddleMouseDown = false
self:OnMousePanButtonReleased(input, processed)
end
function BaseCamera:OnMouseMoved(input, processed)
if not self.hasGameLoaded and VRService.VREnabled then
return
end
local inputDelta = input.Delta
inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * UserGameSettings:GetCameraYInvertValue())
if self.panEnabled and ((self.startPos and self.lastPos and self.panBeginLook) or self.inFirstPerson or self.inMouseLockedMode) then
local desiredXYVector = self:InputTranslationToCameraAngleChange(inputDelta,MOUSE_SENSITIVITY)
self.rotateInput = self.rotateInput + desiredXYVector
end
if self.startPos and self.lastPos and self.panBeginLook then
self.lastPos = self.lastPos + input.Delta
end
end
function BaseCamera:OnMousePanButtonPressed(input, processed)
if processed then return end
self:UpdateMouseBehavior()
self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
self.startPos = self.startPos or input.Position
self.lastPos = self.lastPos or self.startPos
self.userPanningTheCamera = true
end
function BaseCamera:OnMousePanButtonReleased(input, processed)
self:UpdateMouseBehavior()
if not (self.isRightMouseDown or self.isMiddleMouseDown) then
self.panBeginLook = nil
self.startPos = nil
self.lastPos = nil
self.userPanningTheCamera = false
end
end
function BaseCamera:OnMouseWheel(input, processed)
if not self.hasGameLoaded and VRService.VREnabled then
return
end
if not processed then
if self.distanceChangeEnabled then
local wheelInput = Util.Clamp(-1, 1, -input.Position.Z)
local newDistance
if self.inFirstPerson and wheelInput > 0 then
newDistance = FIRST_PERSON_DISTANCE_THRESHOLD
else
-- The 0.156 and 1.7 values are the slope and intercept of a line that is replacing the old
-- rk4Integrator function which was not being used as an integrator, only to get a delta as a function of distance,
-- which was linear as it was being used. These constants preserve the status quo behavior.
newDistance = self.currentSubjectDistance + 0.156 * self.currentSubjectDistance * wheelInput + 1.7 * math.sign(wheelInput)
end
self:SetCameraToSubjectDistance(newDistance)
end
end
end
function BaseCamera:OnKeyDown(input, processed)
if not self.hasGameLoaded and VRService.VREnabled then
return
end
if processed then
return
end
if self.distanceChangeEnabled then
if input.KeyCode == Enum.KeyCode.I then
self:SetCameraToSubjectDistance( self.currentSubjectDistance - 5 )
elseif input.KeyCode == Enum.KeyCode.O then
self:SetCameraToSubjectDistance( self.currentSubjectDistance + 5 )
end
end
if self.panBeginLook == nil and self.keyPanEnabled then
if input.KeyCode == Enum.KeyCode.Left then
self.turningLeft = true
elseif input.KeyCode == Enum.KeyCode.Right then
self.turningRight = true
elseif input.KeyCode == Enum.KeyCode.Comma then
local angle = Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1), -math.pi*0.1875, math.pi*0.25)
if angle ~= 0 then
self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
self.lastUserPanCamera = tick()
self.lastCameraTransform = nil
end
elseif input.KeyCode == Enum.KeyCode.Period then
local angle = Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1), math.pi*0.1875, math.pi*0.25)
if angle ~= 0 then
self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
self.lastUserPanCamera = tick()
self.lastCameraTransform = nil
end
elseif input.KeyCode == Enum.KeyCode.PageUp then
self.rotateInput = self.rotateInput + Vector2.new(0,math.rad(15))
self.lastCameraTransform = nil
elseif input.KeyCode == Enum.KeyCode.PageDown then
self.rotateInput = self.rotateInput + Vector2.new(0,math.rad(-15))
self.lastCameraTransform = nil
end
end
end
function BaseCamera:OnKeyUp(input, processed)
if input.KeyCode == Enum.KeyCode.Left then
self.turningLeft = false
elseif input.KeyCode == Enum.KeyCode.Right then
self.turningRight = false
end
end
function BaseCamera:UpdateMouseBehavior()
-- first time transition to first person mode or mouse-locked third person
if self.inFirstPerson or self.inMouseLockedMode then
pcall(function() UserGameSettings.RotationType = Enum.RotationType.CameraRelative end)
if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
end
else
pcall(function() UserGameSettings.RotationType = Enum.RotationType.MovementRelative end)
if self.isRightMouseDown or self.isMiddleMouseDown then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
function BaseCamera:UpdateForDistancePropertyChange()
-- Calling this setter with the current value will force checking that it is still
-- in range after a change to the min/max distance limits
self:SetCameraToSubjectDistance(self.currentSubjectDistance)
end
function BaseCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
local player = Players.LocalPlayer
-- By default, camera modules will respect LockFirstPerson and override the currentSubjectDistance with 0
-- regardless of what Player.CameraMinZoomDistance is set to, so that first person can be made
-- available by the developer without needing to allow players to mousewheel dolly into first person.
-- Some modules will override this function to remove or change first-person capability.
if player.CameraMode == Enum.CameraMode.LockFirstPerson then
self.currentSubjectDistance = 0.5
if not self.inFirstPerson then
self:EnterFirstPerson()
end
else
local newSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredSubjectDistance)
if newSubjectDistance < FIRST_PERSON_DISTANCE_THRESHOLD then
self.currentSubjectDistance = 0.5
if not self.inFirstPerson then
self:EnterFirstPerson()
end
else
self.currentSubjectDistance = newSubjectDistance
if self.inFirstPerson then
self:LeaveFirstPerson()
end
end
end
-- Returned only for convenience to the caller to know the outcome
return self.currentSubjectDistance
end
function BaseCamera:SetCameraType( cameraType )
--Used by derived classes
self.cameraType = cameraType
end
function BaseCamera:GetCameraType()
return self.cameraType
end
-- Movement mode standardized to Enum.ComputerCameraMovementMode values
function BaseCamera:SetCameraMovementMode( cameraMovementMode )
self.cameraMovementMode = cameraMovementMode
end
function BaseCamera:GetCameraMovementMode()
return self.cameraMovementMode
end
function BaseCamera:SetIsMouseLocked(mouseLocked)
self.inMouseLockedMode = mouseLocked
self:UpdateMouseBehavior()
end
function BaseCamera:GetIsMouseLocked()
return self.inMouseLockedMode
end
function BaseCamera:SetMouseLockOffset(offsetVector)
self.mouseLockOffset = offsetVector
end
function BaseCamera:GetMouseLockOffset()
return self.mouseLockOffset
end
function BaseCamera:InFirstPerson()
return self.inFirstPerson
end
function BaseCamera:EnterFirstPerson()
-- Overriden in ClassicCamera, the only module which supports FirstPerson
end
function BaseCamera:LeaveFirstPerson()
-- Overriden in ClassicCamera, the only module which supports FirstPerson
end
-- Nominal distance, set by dollying in and out with the mouse wheel or equivalent, not measured distance
function BaseCamera:GetCameraToSubjectDistance()
return self.currentSubjectDistance
end
-- Actual measured distance to the camera Focus point, which may be needed in special circumstances, but should
-- never be used as the starting point for updating the nominal camera-to-subject distance (self.currentSubjectDistance)
-- since that is a desired target value set only by mouse wheel (or equivalent) input, PopperCam, and clamped to min max camera distance
function BaseCamera:GetMeasuredDistanceToFocus()
local camera = game.Workspace.CurrentCamera
if camera then
return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
end
return nil
end
function BaseCamera:GetCameraLookVector()
return game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame.lookVector or UNIT_Z
end
-- Replacements for RootCamera:RotateCamera() which did not actually rotate the camera
-- suppliedLookVector is not normally passed in, it's used only by Watch camera
function BaseCamera:CalculateNewLookCFrame(suppliedLookVector)
local currLookVector = suppliedLookVector or self:GetCameraLookVector()
local currPitchAngle = math.asin(currLookVector.y)
local yTheta = Util.Clamp(-MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle, self.rotateInput.y)
local constrainedRotateInput = Vector2.new(self.rotateInput.x, yTheta)
local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
local newLookCFrame = CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)
return newLookCFrame
end
function BaseCamera:CalculateNewLookVector(suppliedLookVector)
local newLookCFrame = self:CalculateNewLookCFrame(suppliedLookVector)
return newLookCFrame.lookVector
end
function BaseCamera:CalculateNewLookVectorVR()
local subjectPosition = self:GetSubjectPosition()
local vecToSubject = (subjectPosition - game.Workspace.CurrentCamera.CFrame.p)
local currLookVector = (vecToSubject * X1_Y0_Z1).unit
local vrRotateInput = Vector2.new(self.rotateInput.x, 0)
local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
local yawRotatedVector = (CFrame.Angles(0, -vrRotateInput.x, 0) * startCFrame * CFrame.Angles(-vrRotateInput.y,0,0)).lookVector
return (yawRotatedVector * X1_Y0_Z1).unit
end
function BaseCamera:GetHumanoid()
local player = Players.LocalPlayer
local character = player and player.Character
if character then
local resultHumanoid = self.humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
self.humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
self.humanoidCache[player] = humanoid
end
return humanoid
end
end
return nil
end
function BaseCamera:GetHumanoidPartToFollow(humanoid, humanoidStateType)
if humanoidStateType == Enum.HumanoidStateType.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
function BaseCamera:UpdateGamepad()
local gamepadPan = self.gamepadPanningCamera
if gamepadPan and (self.hasGameLoaded or not VRService.VREnabled) then
gamepadPan = Util.GamepadLinearToCurve(gamepadPan)
local currentTime = tick()
if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
self.userPanningTheCamera = true
elseif gamepadPan == ZERO_VECTOR2 then
self.lastThumbstickRotate = nil
if self.lastThumbstickPos == ZERO_VECTOR2 then
self.currentSpeed = 0
end
end
local finalConstant = 0
if self.lastThumbstickRotate then
if VRService.VREnabled then
self.currentSpeed = self.vrMaxSpeed
else
local elapsedTime = (currentTime - self.lastThumbstickRotate) * 10
self.currentSpeed = self.currentSpeed + (self.maxSpeed * ((elapsedTime*elapsedTime)/self.numOfSeconds))
if self.currentSpeed > self.maxSpeed then self.currentSpeed = self.maxSpeed end
if self.lastVelocity then
local velocity = (gamepadPan - self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
local velocityDeltaMag = (velocity - self.lastVelocity).magnitude
if velocityDeltaMag > 12 then
self.currentSpeed = self.currentSpeed * (20/velocityDeltaMag)
if self.currentSpeed > self.maxSpeed then self.currentSpeed = self.maxSpeed end
end
end
end
local success, gamepadCameraSensitivity = pcall(function() return UserGameSettings.GamepadCameraSensitivity end)
finalConstant = success and (gamepadCameraSensitivity * self.currentSpeed) or self.currentSpeed
self.lastVelocity = (gamepadPan - self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
end
self.lastThumbstickPos = gamepadPan
self.lastThumbstickRotate = currentTime
return Vector2.new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * self.ySensitivity * UserGameSettings:GetCameraYInvertValue())
end
return ZERO_VECTOR2
end
-- [[ VR Support Section ]] --
function BaseCamera:ApplyVRTransform()
if not VRService.VREnabled then
return
end
--we only want this to happen in first person VR
local rootJoint = self.humanoidRootPart and self.humanoidRootPart:FindFirstChild("RootJoint")
if not rootJoint then
return
end
local cameraSubject = game.Workspace.CurrentCamera.CameraSubject
local isInVehicle = cameraSubject and cameraSubject:IsA("VehicleSeat")
if self.inFirstPerson and not isInVehicle then
local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
local vrRotation = vrFrame - vrFrame.p
rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
else
rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
end
end
function BaseCamera:IsInFirstPerson()
return self.inFirstPerson
end
function BaseCamera:ShouldUseVRRotation()
if not VRService.VREnabled then
return false
end
if not self.VRRotationIntensityAvailable and tick() - self.lastVRRotationIntensityCheckTime < 1 then
return false
end
local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
self.VRRotationIntensityAvailable = success and vrRotationIntensity ~= nil
self.lastVRRotationIntensityCheckTime = tick()
self.shouldUseVRRotation = success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
return self.shouldUseVRRotation
end
function BaseCamera:GetVRRotationInput()
local vrRotateSum = ZERO_VECTOR2
local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
if not success then
return
end
local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
local delayExpired = (tick() - self.lastVRRotationTime) >= self:GetRepeatDelayValue(vrRotationIntensity)
if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
local sign = 1
if vrGamepadRotation.x < 0 then
sign = -1
end
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
end
elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
end
if self.turningLeft then
if delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Left] then
vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
self.vrRotateKeyCooldown[Enum.KeyCode.Left] = true
end
else
self.vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
end
if self.turningRight then
if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Right]) then
vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
self.vrRotateKeyCooldown[Enum.KeyCode.Right] = true
end
else
self.vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
end
if vrRotateSum ~= ZERO_VECTOR2 then
self.lastVRRotationTime = tick()
end
return vrRotateSum
end
function BaseCamera:CancelCameraFreeze(keepConstraints)
if not keepConstraints then
self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 1, self.cameraTranslationConstraints.z)
end
if self.cameraFrozen then
self.trackingHumanoid = nil
self.cameraFrozen = false
end
end
function BaseCamera:StartCameraFreeze(subjectPosition, humanoidToTrack)
if not self.cameraFrozen then
self.humanoidJumpOrigin = subjectPosition
self.trackingHumanoid = humanoidToTrack
self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 0, self.cameraTranslationConstraints.z)
self.cameraFrozen = true
end
end
function BaseCamera:RescaleCameraOffset(newScaleFactor)
self.headHeightR15 = R15_HEAD_OFFSET * newScaleFactor
end
function BaseCamera:OnHumanoidSubjectChildAdded(child)
if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
if self.heightScaleChangedConn then
self.heightScaleChangedConn:Disconnect()
end
self.heightScaleChangedConn = child.Changed:Connect(function(newScaleFactor)
self:RescaleCameraOffset(newScaleFactor)
end)
self:RescaleCameraOffset(child.Value)
end
end
function BaseCamera:OnHumanoidSubjectChildRemoved(child)
if child.Name == "BodyHeightScale" then
self:RescaleCameraOffset(1)
if self.heightScaleChangedConn then
self.heightScaleChangedConn:Disconnect()
self.heightScaleChangedConn = nil
end
end
end
function BaseCamera:OnNewCameraSubject()
if self.subjectStateChangedConn then
self.subjectStateChangedConn:Disconnect()
self.subjectStateChangedConn = nil
end
if self.humanoidChildAddedConn then
self.humanoidChildAddedConn:Disconnect()
self.humanoidChildAddedConn = nil
end
if self.humanoidChildRemovedConn then
self.humanoidChildRemovedConn:Disconnect()
self.humanoidChildRemovedConn = nil
end
if self.heightScaleChangedConn then
self.heightScaleChangedConn:Disconnect()
self.heightScaleChangedConn = nil
end
local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
if self.trackingHumanoid ~= humanoid then
self:CancelCameraFreeze()
end
if humanoid and humanoid:IsA("Humanoid") then
self.humanoidChildAddedConn = humanoid.ChildAdded:Connect(function(child)
self:OnHumanoidSubjectChildAdded(child)
end)
self.humanoidChildRemovedConn = humanoid.ChildRemoved:Connect(function(child)
self:OnHumanoidSubjectChildRemoved(child)
end)
for _, child in pairs(humanoid:GetChildren()) do
self:OnHumanoidSubjectChildAdded(child)
end
self.subjectStateChangedConn = humanoid.StateChanged:Connect(function(oldState, newState)
if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not self.inFirstPerson then
self:StartCameraFreeze(self:GetSubjectPosition(), humanoid)
elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
self:CancelCameraFreeze(true)
end
end)
end
end
function BaseCamera:GetVRFocus(subjectPosition, timeDelta)
local lastFocus = self.LastCameraFocus or subjectPosition
if not self.cameraFrozen then
self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, math.min(1, self.cameraTranslationConstraints.y + 0.42 * timeDelta), self.cameraTranslationConstraints.z)
end
local newFocus
if self.cameraFrozen and self.humanoidJumpOrigin and self.humanoidJumpOrigin.y > lastFocus.y then
newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(self.humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
else
newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, self.cameraTranslationConstraints.y))
end
if self.cameraFrozen then
-- No longer in 3rd person
if self.inFirstPerson then -- not VRService.VREnabled
self: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 self.humanoidJumpOrigin and subjectPosition.y < (self.humanoidJumpOrigin.y - 0.5) then
self:CancelCameraFreeze()
end
end
return newFocus
end
function BaseCamera: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 BaseCamera: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
function BaseCamera:Test()
print("BaseCamera:Test()")
end
function BaseCamera:Update(dt)
warn("BaseCamera:Update() This is a virtual function that should never be getting called.")
return game.Workspace.CurrentCamera.CFrame, game.Workspace.CurrentCamera.Focus
end
return BaseCamera