1678 lines
55 KiB
Lua
1678 lines
55 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
|
|
|
|
local CAMERA_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
|
|
|
|
-- 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 TOUCH_ADJUST_AREA_UP = math.rad(30)
|
|
local TOUCH_ADJUST_AREA_DOWN = math.rad(-15)
|
|
|
|
local TOUCH_SENSITIVTY_ADJUST_MAX_Y = 2.1
|
|
local TOUCH_SENSITIVTY_ADJUST_MIN_Y = 0.5
|
|
|
|
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.00945 * math.pi, 0.003375 * math.pi)
|
|
local MOUSE_SENSITIVITY = Vector2.new( 0.002 * math.pi, 0.0015 * math.pi )
|
|
|
|
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, 1.5, 0)
|
|
local R15_HEAD_OFFSET_NO_SCALING = Vector3.new(0, 2, 0)
|
|
local HUMANOID_ROOT_PART_SIZE = Vector3.new(2, 2, 1)
|
|
|
|
local GAMEPAD_ZOOM_STEP_1 = 0
|
|
local GAMEPAD_ZOOM_STEP_2 = 10
|
|
local GAMEPAD_ZOOM_STEP_3 = 20
|
|
|
|
local PAN_SENSITIVITY = 20
|
|
local ZOOM_SENSITIVITY_CURVATURE = 0.5
|
|
|
|
local abs = math.abs
|
|
local sign = math.sign
|
|
|
|
local FFlagUserCameraToggle do
|
|
local success, result = pcall(function()
|
|
return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
|
|
end)
|
|
FFlagUserCameraToggle = success and result
|
|
end
|
|
|
|
local FFlagUserFixZoomInZoomOutDiscrepancy do
|
|
local success, result = pcall(function()
|
|
return UserSettings():IsUserFeatureEnabled("UserFixZoomInZoomOutDiscrepancy")
|
|
end)
|
|
FFlagUserFixZoomInZoomOutDiscrepancy = success and result
|
|
end
|
|
|
|
local FFlagUserCameraInputRefactor do
|
|
local success, result = pcall(function()
|
|
return UserSettings():IsUserFeatureEnabled("UserCameraInputRefactor2")
|
|
end)
|
|
FFlagUserCameraInputRefactor = success and result
|
|
end
|
|
|
|
local Util = require(script.Parent:WaitForChild("CameraUtils"))
|
|
local ZoomController = require(script.Parent:WaitForChild("ZoomController"))
|
|
local CameraToggleStateController = require(script.Parent:WaitForChild("CameraToggleStateController"))
|
|
local CameraInput = require(script.Parent:WaitForChild("CameraInput"))
|
|
local CameraUI = require(script.Parent:WaitForChild("CameraUI"))
|
|
|
|
--[[ 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")
|
|
|
|
local player = Players.LocalPlayer
|
|
|
|
--[[ 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
|
|
|
|
self.lastCameraTransform = nil
|
|
self.rotateInput = ZERO_VECTOR2 -- Remove on FFlagUserCameraInputRefactor
|
|
self.userPanningCamera = false -- Remove on FFlagUserCameraInputRefactor
|
|
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 = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
|
|
self.currentSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
|
|
|
|
self.inFirstPerson = false
|
|
self.inMouseLockedMode = false
|
|
self.portraitMode = false
|
|
self.isSmallTouchScreen = false
|
|
|
|
-- Used by modules which want to reset the camera angle on respawn.
|
|
self.resetCameraAngle = true
|
|
|
|
self.enabled = false
|
|
|
|
-- Input Event Connections
|
|
-- Remove the following block on FFlagUserCameraInputRefactor
|
|
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
|
|
-- End FFlagUserCameraInputRefactor removal block
|
|
|
|
self.PlayerGui = nil
|
|
|
|
self.cameraChangedConn = nil
|
|
self.viewportSizeChangedConn = nil
|
|
|
|
self.boundContextActions = {} -- Remove on FFlagUserCameraInputRefactor
|
|
|
|
-- 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.subjectStateChangedConn = nil
|
|
|
|
self.gamepadZoomPressConnection = nil
|
|
|
|
-- Gamepad support
|
|
-- Remove the following block on FFlagUserCameraInputRefactor
|
|
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
|
|
-- End FFlagUserCameraInputRefactor removal block
|
|
|
|
-- Touch input support
|
|
-- Remove the following block on FFlagUserCameraInputRefactor
|
|
self.isDynamicThumbstickEnabled = false
|
|
self.fingerTouches = {}
|
|
self.dynamicTouchInput = nil
|
|
self.numUnsunkTouches = 0
|
|
self.inputStartPositions = {}
|
|
self.inputStartTimes = {}
|
|
self.startingDiff = nil
|
|
self.pinchBeginZoom = nil
|
|
self.userPanningTheCamera = false
|
|
self.touchActivateConn = nil
|
|
-- End FFlagUserCameraInputRefactor removal block
|
|
|
|
-- Mouse locked formerly known as shift lock mode
|
|
self.mouseLockOffset = ZERO_VECTOR3
|
|
|
|
-- 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)
|
|
self:OnCurrentCameraChanged()
|
|
|
|
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
|
|
|
|
self:OnPlayerCameraPropertyChange()
|
|
|
|
return self
|
|
end
|
|
|
|
function BaseCamera:GetModuleName()
|
|
return "BaseCamera"
|
|
end
|
|
|
|
function BaseCamera:OnCharacterAdded(char)
|
|
self.resetCameraAngle = self.resetCameraAngle or self:GetEnabled()
|
|
self.humanoidRootPart = nil
|
|
if UserInputService.TouchEnabled then
|
|
self.PlayerGui = player:WaitForChild("PlayerGui")
|
|
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
|
|
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
|
|
if humanoid.RigType == Enum.HumanoidRigType.R15 then
|
|
if humanoid.AutomaticScalingEnabled then
|
|
heightOffset = R15_HEAD_OFFSET
|
|
if bodyPartToFollow == humanoid.RootPart then
|
|
local rootPartSizeOffset = (humanoid.RootPart.Size.Y/2) - (HUMANOID_ROOT_PART_SIZE.Y/2)
|
|
heightOffset = heightOffset + Vector3.new(0, rootPartSizeOffset, 0)
|
|
end
|
|
else
|
|
heightOffset = R15_HEAD_OFFSET_NO_SCALING
|
|
end
|
|
else
|
|
heightOffset = HEAD_OFFSET
|
|
end
|
|
|
|
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()
|
|
if self.portraitMode then
|
|
self.defaultSubjectDistance = math.clamp(PORTRAIT_DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
|
|
else
|
|
self.defaultSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
|
|
end
|
|
end
|
|
|
|
function BaseCamera:OnViewportSizeChanged()
|
|
local camera = game.Workspace.CurrentCamera
|
|
local size = camera.ViewportSize
|
|
self.portraitMode = size.X < size.Y
|
|
self.isSmallTouchScreen = UserInputService.TouchEnabled and (size.Y < 500 or size.X < 700)
|
|
|
|
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 player.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
|
|
if (UserGameSettings.TouchMovementMode == Enum.TouchMovementMode.DynamicThumbstick
|
|
or UserGameSettings.TouchMovementMode == Enum.TouchMovementMode.Default) then
|
|
self:OnDynamicThumbstickEnabled()
|
|
else
|
|
self:OnDynamicThumbstickDisabled()
|
|
end
|
|
end
|
|
end
|
|
|
|
function BaseCamera:OnDevTouchMovementModeChanged()
|
|
if player.DevTouchMovementMode == Enum.DevTouchMovementMode.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)
|
|
return translationVector * sensitivity
|
|
end
|
|
|
|
function BaseCamera:GamepadZoomPress()
|
|
local dist = self:GetCameraToSubjectDistance()
|
|
|
|
if dist > (GAMEPAD_ZOOM_STEP_2 + GAMEPAD_ZOOM_STEP_3)/2 then
|
|
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_2)
|
|
elseif dist > (GAMEPAD_ZOOM_STEP_1 + GAMEPAD_ZOOM_STEP_2)/2 then
|
|
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_1)
|
|
else
|
|
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_3)
|
|
end
|
|
end
|
|
|
|
function BaseCamera:Enable(enable)
|
|
if self.enabled ~= enable then
|
|
self.enabled = enable
|
|
if self.enabled then
|
|
if FFlagUserCameraInputRefactor then
|
|
CameraInput.setInputEnabled(true)
|
|
|
|
self.gamepadZoomPressConnection = CameraInput.gamepadZoomPress:Connect(function()
|
|
self:GamepadZoomPress()
|
|
end)
|
|
else
|
|
self:ConnectInputEvents()
|
|
self:BindContextActions()
|
|
end
|
|
|
|
if player.CameraMode == Enum.CameraMode.LockFirstPerson then
|
|
self.currentSubjectDistance = 0.5
|
|
if not self.inFirstPerson then
|
|
self:EnterFirstPerson()
|
|
end
|
|
end
|
|
else
|
|
if FFlagUserCameraInputRefactor then
|
|
CameraInput.setInputEnabled(false)
|
|
|
|
if self.gamepadZoomPressConnection then
|
|
self.gamepadZoomPressConnection:Disconnect()
|
|
self.gamepadZoomPressConnection = nil
|
|
end
|
|
else
|
|
self:DisconnectInputEvents()
|
|
self:UnbindContextActions()
|
|
end
|
|
-- Clean up additional event listeners and reset a bunch of properties
|
|
self:Cleanup()
|
|
end
|
|
end
|
|
end
|
|
|
|
function BaseCamera:GetEnabled()
|
|
return self.enabled
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
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
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
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)
|
|
end
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
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
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnPointerAction(wheel, pan, pinch, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if processed then
|
|
return
|
|
end
|
|
|
|
if pan.Magnitude > 0 then
|
|
local inversionVector = Vector2.new(1, UserGameSettings:GetCameraYInvertValue())
|
|
local rotateDelta = self:InputTranslationToCameraAngleChange(PAN_SENSITIVITY*pan, MOUSE_SENSITIVITY)*inversionVector
|
|
self.rotateInput = self.rotateInput + rotateDelta
|
|
end
|
|
|
|
local zoom = self.currentSubjectDistance
|
|
local zoomDelta = -(wheel + pinch)
|
|
|
|
if abs(zoomDelta) > 0 then
|
|
local newZoom
|
|
if self.inFirstPerson and zoomDelta > 0 then
|
|
newZoom = FIRST_PERSON_DISTANCE_THRESHOLD
|
|
else
|
|
if FFlagUserFixZoomInZoomOutDiscrepancy then
|
|
if (zoomDelta > 0) then
|
|
newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
|
|
else
|
|
newZoom = (zoom + zoomDelta) / (1 - zoomDelta*ZOOM_SENSITIVITY_CURVATURE)
|
|
end
|
|
else
|
|
newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
|
|
end
|
|
end
|
|
|
|
self:SetCameraToSubjectDistance(newZoom)
|
|
end
|
|
end
|
|
|
|
function BaseCamera:ConnectInputEvents()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
self.pointerActionConn = UserInputService.PointerAction:Connect(function(wheel, pan, pinch, processed)
|
|
self:OnPointerAction(wheel, pan, pinch, processed)
|
|
end)
|
|
|
|
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.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:AssignActivateGamepad()
|
|
if not FFlagUserCameraToggle then
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:BindContextActions()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
self:BindGamepadInputActions()
|
|
self:BindKeyboardInputActions()
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:AssignActivateGamepad()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:DisconnectInputEvents()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:UnbindContextActions()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
for i = 1, #self.boundContextActions do
|
|
ContextActionService:UnbindAction(self.boundContextActions[i])
|
|
end
|
|
self.boundContextActions = {}
|
|
end
|
|
|
|
function BaseCamera:Cleanup()
|
|
if self.pointerActionConn then
|
|
self.pointerActionConn:Disconnect()
|
|
self.pointerActionConn = nil
|
|
end
|
|
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()
|
|
if self.gamepadPanningCamera then
|
|
self.gamepadPanningCamera = ZERO_VECTOR2
|
|
end
|
|
|
|
-- Reset input states
|
|
self.startPos = nil
|
|
self.lastPos = nil
|
|
self.panBeginLook = nil
|
|
self.isRightMouseDown = false
|
|
self.isMiddleMouseDown = false
|
|
|
|
self.fingerTouches = {}
|
|
self.dynamicTouchInput = nil
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
-- This is called when settings menu is opened
|
|
function BaseCamera:ResetInputStates()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
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.dynamicTouchInput = nil
|
|
self.panBeginLook = nil
|
|
self.startPos = nil
|
|
self.lastPos = nil
|
|
self.userPanningTheCamera = false
|
|
self.startingDiff = nil
|
|
self.pinchBeginZoom = nil
|
|
self.numUnsunkTouches = 0
|
|
end
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:GetGamepadPan(name, state, input)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
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
|
|
return Enum.ContextActionResult.Sink
|
|
end
|
|
return Enum.ContextActionResult.Pass
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:DoKeyboardPanTurn(name, state, input)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if not self.hasGameLoaded and VRService.VREnabled then
|
|
return Enum.ContextActionResult.Pass
|
|
end
|
|
|
|
if state == Enum.UserInputState.Cancel then
|
|
self.turningLeft = false
|
|
self.turningRight = false
|
|
return Enum.ContextActionResult.Sink
|
|
end
|
|
|
|
if self.panBeginLook == nil and self.keyPanEnabled then
|
|
if input.KeyCode == Enum.KeyCode.Left then
|
|
self.turningLeft = state == Enum.UserInputState.Begin
|
|
elseif input.KeyCode == Enum.KeyCode.Right then
|
|
self.turningRight = state == Enum.UserInputState.Begin
|
|
end
|
|
return Enum.ContextActionResult.Sink
|
|
end
|
|
return Enum.ContextActionResult.Pass
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:DoGamepadZoom(name, state, input)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if input.UserInputType == self.activeGamepad then
|
|
if input.KeyCode == Enum.KeyCode.ButtonR3 then
|
|
if state == Enum.UserInputState.Begin then
|
|
if self.distanceChangeEnabled then
|
|
local dist = self:GetCameraToSubjectDistance()
|
|
|
|
if dist > (GAMEPAD_ZOOM_STEP_2 + GAMEPAD_ZOOM_STEP_3)/2 then
|
|
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_2)
|
|
elseif dist > (GAMEPAD_ZOOM_STEP_1 + GAMEPAD_ZOOM_STEP_2)/2 then
|
|
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_1)
|
|
else
|
|
self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_3)
|
|
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
|
|
return Enum.ContextActionResult.Sink
|
|
end
|
|
return Enum.ContextActionResult.Pass
|
|
-- 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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:DoKeyboardZoom(name, state, input)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if not self.hasGameLoaded and VRService.VREnabled then
|
|
return Enum.ContextActionResult.Pass
|
|
end
|
|
|
|
if state ~= Enum.UserInputState.Begin then
|
|
return Enum.ContextActionResult.Pass
|
|
end
|
|
|
|
if self.distanceChangeEnabled and player.CameraMode ~= Enum.CameraMode.LockFirstPerson 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
|
|
return Enum.ContextActionResult.Sink
|
|
end
|
|
return Enum.ContextActionResult.Pass
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:BindAction(actionName, actionFunc, createTouchButton, ...)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
table.insert(self.boundContextActions, actionName)
|
|
ContextActionService:BindActionAtPriority(actionName, actionFunc, createTouchButton,
|
|
CAMERA_ACTION_PRIORITY, ...)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:BindGamepadInputActions()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
self:BindAction("BaseCameraGamepadPan", function(name, state, input) return self:GetGamepadPan(name, state, input) end,
|
|
false, Enum.KeyCode.Thumbstick2)
|
|
self:BindAction("BaseCameraGamepadZoom", function(name, state, input) return self:DoGamepadZoom(name, state, input) end,
|
|
false, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight, Enum.KeyCode.ButtonR3)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:BindKeyboardInputActions()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
self:BindAction("BaseCameraKeyboardPanArrowKeys", function(name, state, input) return self:DoKeyboardPanTurn(name, state, input) end,
|
|
false, Enum.KeyCode.Left, Enum.KeyCode.Right)
|
|
self:BindAction("BaseCameraKeyboardZoom", function(name, state, input) return self:DoKeyboardZoom(name, state, input) end,
|
|
false, Enum.KeyCode.I, Enum.KeyCode.O)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
local function isInDynamicThumbstickArea(input)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
local playerGui = player:FindFirstChildOfClass("PlayerGui")
|
|
local touchGui = playerGui and playerGui:FindFirstChild("TouchGui")
|
|
local touchFrame = touchGui and touchGui:FindFirstChild("TouchControlFrame")
|
|
local thumbstickFrame = touchFrame and touchFrame:FindFirstChild("DynamicThumbstickFrame")
|
|
|
|
if not thumbstickFrame then
|
|
return false
|
|
end
|
|
|
|
local frameCornerTopLeft = thumbstickFrame.AbsolutePosition
|
|
local frameCornerBottomRight = frameCornerTopLeft + thumbstickFrame.AbsoluteSize
|
|
if input.Position.X >= frameCornerTopLeft.X and input.Position.Y >= frameCornerTopLeft.Y then
|
|
if input.Position.X <= frameCornerBottomRight.X and input.Position.Y <= frameCornerBottomRight.Y then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
---Adjusts the camera Y touch Sensitivity when moving away from the center and in the TOUCH_SENSITIVTY_ADJUST_AREA
|
|
function BaseCamera:AdjustTouchSensitivity(delta, sensitivity)
|
|
local cameraCFrame = game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame
|
|
if not cameraCFrame then
|
|
return sensitivity
|
|
end
|
|
local currPitchAngle = cameraCFrame:ToEulerAnglesYXZ()
|
|
|
|
local multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y
|
|
if currPitchAngle > TOUCH_ADJUST_AREA_UP and delta.Y < 0 then
|
|
local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_UP)/(MAX_Y - TOUCH_ADJUST_AREA_UP)
|
|
fractionAdjust = 1 - (1 - fractionAdjust)^3
|
|
multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
|
|
TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
|
|
elseif currPitchAngle < TOUCH_ADJUST_AREA_DOWN and delta.Y > 0 then
|
|
local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_DOWN)/(MIN_Y - TOUCH_ADJUST_AREA_DOWN)
|
|
fractionAdjust = 1 - (1 - fractionAdjust)^3
|
|
multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
|
|
TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
|
|
end
|
|
|
|
return Vector2.new(
|
|
sensitivity.X,
|
|
sensitivity.Y * multiplierY
|
|
)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnTouchBegan(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
local canUseDynamicTouch = self.isDynamicThumbstickEnabled and not processed
|
|
if canUseDynamicTouch then
|
|
if self.dynamicTouchInput == nil and isInDynamicThumbstickArea(input) then
|
|
-- First input in the dynamic thumbstick area should always be ignored for camera purposes
|
|
-- Even if the dynamic thumbstick does not process it immediately
|
|
self.dynamicTouchInput = input
|
|
return
|
|
end
|
|
self.fingerTouches[input] = processed
|
|
self.inputStartPositions[input] = input.Position
|
|
self.inputStartTimes[input] = tick()
|
|
self.numUnsunkTouches = self.numUnsunkTouches + 1
|
|
end
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnTouchChanged(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
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 adjustedTouchSensitivity = TOUCH_SENSITIVTY
|
|
self:AdjustTouchSensitivity(delta, TOUCH_SENSITIVTY)
|
|
|
|
local desiredXYVector = self:InputTranslationToCameraAngleChange(delta, adjustedTouchSensitivity)
|
|
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 = math.clamp(scale, 0.1, 10)
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnTouchEnded(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if input == self.dynamicTouchInput then
|
|
self.dynamicTouchInput = nil
|
|
return
|
|
end
|
|
|
|
if self.fingerTouches[input] == false then
|
|
if self.numUnsunkTouches == 1 then
|
|
self.panBeginLook = nil
|
|
self.startPos = nil
|
|
self.lastPos = nil
|
|
self.userPanningTheCamera = false
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMouse2Down(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if processed then return end
|
|
|
|
self.isRightMouseDown = true
|
|
self:OnMousePanButtonPressed(input, processed)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMouse2Up(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
self.isRightMouseDown = false
|
|
self:OnMousePanButtonReleased(input, processed)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMouse3Down(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if processed then return end
|
|
|
|
self.isMiddleMouseDown = true
|
|
self:OnMousePanButtonPressed(input, processed)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMouse3Up(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
self.isMiddleMouseDown = false
|
|
self:OnMousePanButtonReleased(input, processed)
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMouseMoved(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if not self.hasGameLoaded and VRService.VREnabled then
|
|
return
|
|
end
|
|
|
|
local inputDelta = input.Delta
|
|
inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * UserGameSettings:GetCameraYInvertValue())
|
|
|
|
local isInputPanning = FFlagUserCameraToggle and CameraInput.getPanning()
|
|
local isBeginLook = self.startPos and self.lastPos and self.panBeginLook
|
|
local isPanning = isBeginLook or self.inFirstPerson or self.inMouseLockedMode or isInputPanning
|
|
|
|
if self.panEnabled and isPanning 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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMousePanButtonPressed(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if processed then return end
|
|
if not FFlagUserCameraToggle then
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:OnMousePanButtonReleased(input, processed)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
if not FFlagUserCameraToggle then
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
if not (self.isRightMouseDown or self.isMiddleMouseDown) then
|
|
self.panBeginLook = nil
|
|
self.startPos = nil
|
|
self.lastPos = nil
|
|
self.userPanningTheCamera = false
|
|
end
|
|
end
|
|
|
|
function BaseCamera:UpdateMouseBehavior()
|
|
if FFlagUserCameraToggle and self.isCameraToggle then
|
|
CameraUI.setCameraModeToastEnabled(true)
|
|
CameraInput.enableCameraToggleInput()
|
|
CameraToggleStateController(self.inFirstPerson)
|
|
else
|
|
if FFlagUserCameraToggle then
|
|
CameraUI.setCameraModeToastEnabled(false)
|
|
CameraInput.disableCameraToggleInput()
|
|
end
|
|
-- first time transition to first person mode or mouse-locked third person
|
|
if self.inFirstPerson or self.inMouseLockedMode then
|
|
UserGameSettings.RotationType = Enum.RotationType.CameraRelative
|
|
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
|
|
else
|
|
UserGameSettings.RotationType = Enum.RotationType.MovementRelative
|
|
if self.isRightMouseDown or self.isMiddleMouseDown then
|
|
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
|
|
else
|
|
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
|
|
end
|
|
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 lastSubjectDistance = self.currentSubjectDistance
|
|
|
|
-- 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 = math.clamp(desiredSubjectDistance, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
|
|
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
|
|
|
|
-- Pass target distance and zoom direction to the zoom controller
|
|
ZoomController.SetZoomParameters(self.currentSubjectDistance, math.sign(desiredSubjectDistance - lastSubjectDistance))
|
|
|
|
-- 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
|
|
if not FFlagUserCameraToggle then
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
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()
|
|
-- Overridden in ClassicCamera, the only module which supports FirstPerson
|
|
end
|
|
|
|
function BaseCamera:LeaveFirstPerson()
|
|
-- Overridden 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
|
|
|
|
function BaseCamera:CalculateNewLookCFrameFromArg(suppliedLookVector, rotateInput)
|
|
local currLookVector = suppliedLookVector or self:GetCameraLookVector()
|
|
local currPitchAngle = math.asin(currLookVector.y)
|
|
local yTheta = math.clamp(rotateInput.y, -MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle)
|
|
local constrainedRotateInput = Vector2.new(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:CalculateNewLookVectorFromArg(suppliedLookVector, rotateInput)
|
|
local newLookCFrame = self:CalculateNewLookCFrameFromArg(suppliedLookVector, rotateInput)
|
|
return newLookCFrame.lookVector
|
|
end
|
|
|
|
function BaseCamera:CalculateNewLookVectorVRFromArg(rotateInput)
|
|
local subjectPosition = self:GetSubjectPosition()
|
|
local vecToSubject = (subjectPosition - game.Workspace.CurrentCamera.CFrame.p)
|
|
local currLookVector = (vecToSubject * X1_Y0_Z1).unit
|
|
local vrRotateInput = Vector2.new(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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
-- 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)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
local currLookVector = suppliedLookVector or self:GetCameraLookVector()
|
|
local currPitchAngle = math.asin(currLookVector.y)
|
|
local yTheta = math.clamp(self.rotateInput.y, -MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle)
|
|
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
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:CalculateNewLookVector(suppliedLookVector)
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
local newLookCFrame = self:CalculateNewLookCFrame(suppliedLookVector)
|
|
return newLookCFrame.lookVector
|
|
end
|
|
|
|
-- Remove on FFlagUserCameraInputRefactor
|
|
function BaseCamera:CalculateNewLookVectorVR()
|
|
assert(not FFlagUserCameraInputRefactor)
|
|
|
|
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 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.userPanningTheCamera = false
|
|
self.gamepadPanningCamera = false
|
|
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
|
|
|
|
finalConstant = UserGameSettings.GamepadCameraSensitivity * 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:OnNewCameraSubject()
|
|
if self.subjectStateChangedConn then
|
|
self.subjectStateChangedConn:Disconnect()
|
|
self.subjectStateChangedConn = 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.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:Update(dt)
|
|
error("BaseCamera:Update() This is a virtual function that should never be getting called.", 2)
|
|
end
|
|
|
|
return BaseCamera
|