241 lines
8.4 KiB
Lua
241 lines
8.4 KiB
Lua
--[[
|
|
ClassicCamera - Classic Roblox camera control module
|
|
2018 Camera Update - AllYourBlox
|
|
|
|
Note: This module also handles camera control types Follow and Track, the
|
|
latter of which is currently not distinguished from Classic
|
|
--]]
|
|
|
|
-- Local private variables and constants
|
|
local ZERO_VECTOR2 = Vector2.new(0,0)
|
|
|
|
local tweenAcceleration = math.rad(220) --Radians/Second^2
|
|
local tweenSpeed = math.rad(0) --Radians/Second
|
|
local tweenMaxSpeed = math.rad(250) --Radians/Second
|
|
local TIME_BEFORE_AUTO_ROTATE = 2.0 --Seconds, used when auto-aligning camera with vehicles
|
|
local PORTRAIT_OFFSET = Vector3.new(0,-3,0)
|
|
|
|
--[[ Services ]]--
|
|
local PlayersService = game:GetService('Players')
|
|
local VRService = game:GetService("VRService")
|
|
|
|
local Util = require(script.Parent:WaitForChild("CameraUtils"))
|
|
|
|
--[[ The Module ]]--
|
|
local BaseCamera = require(script.Parent:WaitForChild("BaseCamera"))
|
|
local ClassicCamera = setmetatable({}, BaseCamera)
|
|
ClassicCamera.__index = ClassicCamera
|
|
|
|
function ClassicCamera.new()
|
|
local self = setmetatable(BaseCamera.new(), ClassicCamera)
|
|
|
|
self.isFollowCamera = false
|
|
self.lastUpdate = tick()
|
|
|
|
return self
|
|
end
|
|
|
|
function ClassicCamera:GetModuleName()
|
|
return "ClassicCamera"
|
|
end
|
|
|
|
-- Movement mode standardized to Enum.ComputerCameraMovementMode values
|
|
function ClassicCamera:SetCameraMovementMode( cameraMovementMode )
|
|
BaseCamera.SetCameraMovementMode(self,cameraMovementMode)
|
|
self.isFollowCamera = cameraMovementMode == Enum.ComputerCameraMovementMode.Follow
|
|
end
|
|
|
|
function ClassicCamera:Test()
|
|
print("ClassicCamera:Test()")
|
|
end
|
|
|
|
function ClassicCamera:Update()
|
|
local now = tick()
|
|
local timeDelta = (now - self.lastUpdate)
|
|
|
|
local camera = workspace.CurrentCamera
|
|
local newCameraCFrame = camera.CFrame
|
|
local newCameraFocus = camera.Focus
|
|
local player = PlayersService.LocalPlayer
|
|
local humanoid = self:GetHumanoid()
|
|
local cameraSubject = camera.CameraSubject
|
|
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
|
|
local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
|
|
local isClimbing = humanoid and humanoid:GetState() == Enum.HumanoidStateType.Climbing
|
|
|
|
if self.lastUpdate == nil or timeDelta > 1 then
|
|
self.lastCameraTransform = nil
|
|
end
|
|
|
|
if self.lastUpdate then
|
|
local gamepadRotation = self:UpdateGamepad()
|
|
|
|
if self:ShouldUseVRRotation() then
|
|
self.rotateInput = self.rotateInput + self:GetVRRotationInput()
|
|
else
|
|
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
|
|
local delta = math.min(0.1, timeDelta)
|
|
|
|
if gamepadRotation ~= ZERO_VECTOR2 then
|
|
self.rotateInput = self.rotateInput + (gamepadRotation * delta)
|
|
end
|
|
|
|
local angle = 0
|
|
if not (isInVehicle or isOnASkateboard) then
|
|
angle = angle + (self.turningLeft and -120 or 0)
|
|
angle = angle + (self.turningRight and 120 or 0)
|
|
end
|
|
|
|
if angle ~= 0 then
|
|
self.rotateInput = self.rotateInput + Vector2.new(math.rad(angle * delta), 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Reset tween speed if user is panning
|
|
if self.userPanningTheCamera then
|
|
tweenSpeed = 0
|
|
self.lastUserPanCamera = tick()
|
|
end
|
|
|
|
local userRecentlyPannedCamera = now - self.lastUserPanCamera < TIME_BEFORE_AUTO_ROTATE
|
|
local subjectPosition = self:GetSubjectPosition()
|
|
|
|
if subjectPosition and player and camera then
|
|
local zoom = self:GetCameraToSubjectDistance()
|
|
if zoom < 0.5 then
|
|
zoom = 0.5
|
|
end
|
|
|
|
if self:GetIsMouseLocked() and not self:IsInFirstPerson() then
|
|
-- We need to use the right vector of the camera after rotation, not before
|
|
local newLookCFrame = self:CalculateNewLookCFrame()
|
|
|
|
local offset = self:GetMouseLockOffset()
|
|
local cameraRelativeOffset = offset.X * newLookCFrame.rightVector + offset.Y * newLookCFrame.upVector + offset.Z * newLookCFrame.lookVector
|
|
|
|
--offset can be NAN, NAN, NAN if newLookVector has only y component
|
|
if Util.IsFiniteVector3(cameraRelativeOffset) then
|
|
subjectPosition = subjectPosition + cameraRelativeOffset
|
|
end
|
|
else
|
|
if not self.userPanningTheCamera and self.lastCameraTransform then
|
|
|
|
local isInFirstPerson = self:IsInFirstPerson()
|
|
|
|
if (isInVehicle or isOnASkateboard or (self.isFollowCamera and isClimbing)) and self.lastUpdate and humanoid and humanoid.Torso then
|
|
if isInFirstPerson then
|
|
if self.lastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
|
|
local y = -Util.GetAngleBetweenXZVectors(self.lastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
|
|
if Util.IsFinite(y) then
|
|
self.rotateInput = self.rotateInput + Vector2.new(y, 0)
|
|
end
|
|
tweenSpeed = 0
|
|
end
|
|
elseif not userRecentlyPannedCamera then
|
|
local forwardVector = humanoid.Torso.CFrame.lookVector
|
|
if isOnASkateboard then
|
|
forwardVector = cameraSubject.CFrame.lookVector
|
|
end
|
|
|
|
tweenSpeed = Util.Clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
|
|
|
|
local percent = Util.Clamp(0, 1, tweenSpeed * timeDelta)
|
|
if self:IsInFirstPerson() and not (self.isFollowCamera and self.isClimbing) then
|
|
percent = 1
|
|
end
|
|
|
|
local y = Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
|
|
if Util.IsFinite(y) and math.abs(y) > 0.0001 then
|
|
self.rotateInput = self.rotateInput + Vector2.new(y * percent, 0)
|
|
end
|
|
end
|
|
|
|
elseif self.isFollowCamera and (not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled) then
|
|
-- Logic that was unique to the old FollowCamera module
|
|
local lastVec = -(self.lastCameraTransform.p - subjectPosition)
|
|
|
|
local y = Util.GetAngleBetweenXZVectors(lastVec, self:GetCameraLookVector())
|
|
|
|
-- This cutoff is to decide if the humanoid's angle of movement,
|
|
-- relative to the camera's look vector, is enough that
|
|
-- we want the camera to be following them. The point is to provide
|
|
-- a sizable deadzone to allow more precise forward movements.
|
|
local thetaCutoff = 0.4
|
|
|
|
-- Check for NaNs
|
|
if Util.IsFinite(y) and math.abs(y) > 0.0001 and math.abs(y) > thetaCutoff * timeDelta then
|
|
self.rotateInput = self.rotateInput + Vector2.new(y, 0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not self.isFollowCamera then
|
|
local VREnabled = VRService.VREnabled
|
|
newCameraFocus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame.new(subjectPosition)
|
|
|
|
local cameraFocusP = newCameraFocus.p
|
|
if VREnabled and not self:IsInFirstPerson() then
|
|
local cameraHeight = self:GetCameraHeight()
|
|
local vecToSubject = (subjectPosition - camera.CFrame.p)
|
|
local distToSubject = vecToSubject.magnitude
|
|
|
|
-- Only move the camera if it exceeded a maximum distance to the subject in VR
|
|
if distToSubject > zoom or self.rotateInput.x ~= 0 then
|
|
local desiredDist = math.min(distToSubject, zoom)
|
|
vecToSubject = self:CalculateNewLookVectorVR() * desiredDist
|
|
local newPos = cameraFocusP - vecToSubject
|
|
local desiredLookDir = camera.CFrame.lookVector
|
|
if self.rotateInput.x ~= 0 then
|
|
desiredLookDir = vecToSubject
|
|
end
|
|
local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
|
|
self.rotateInput = ZERO_VECTOR2
|
|
|
|
newCameraCFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
|
|
end
|
|
else
|
|
local newLookVector = self:CalculateNewLookVector()
|
|
self.rotateInput = ZERO_VECTOR2
|
|
newCameraCFrame = CFrame.new(cameraFocusP - (zoom * newLookVector), cameraFocusP)
|
|
end
|
|
else -- is FollowCamera
|
|
local newLookVector = self:CalculateNewLookVector()
|
|
self.rotateInput = ZERO_VECTOR2
|
|
|
|
if VRService.VREnabled then
|
|
newCameraFocus = self:GetVRFocus(subjectPosition, timeDelta)
|
|
elseif self.portraitMode then
|
|
newCameraFocus = CFrame.new(subjectPosition + PORTRAIT_OFFSET)
|
|
else
|
|
newCameraFocus = CFrame.new(subjectPosition)
|
|
end
|
|
newCameraCFrame = CFrame.new(newCameraFocus.p - (zoom * newLookVector), newCameraFocus.p) + Vector3.new(0, self:GetCameraHeight(), 0)
|
|
end
|
|
|
|
self.lastCameraTransform = newCameraCFrame
|
|
self.lastCameraFocus = newCameraFocus
|
|
if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
|
|
self.lastSubjectCFrame = cameraSubject.CFrame
|
|
else
|
|
self.lastSubjectCFrame = nil
|
|
end
|
|
end
|
|
|
|
self.lastUpdate = now
|
|
return newCameraCFrame, newCameraFocus
|
|
end
|
|
|
|
function ClassicCamera:EnterFirstPerson()
|
|
self.inFirstPerson = true
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
|
|
function ClassicCamera:LeaveFirstPerson()
|
|
self.inFirstPerson = false
|
|
self:UpdateMouseBehavior()
|
|
end
|
|
|
|
return ClassicCamera
|