193 lines
6.6 KiB
Lua
193 lines
6.6 KiB
Lua
local PlayersService = game:GetService('Players')
|
|
local VRService = game:GetService("VRService")
|
|
|
|
local RootCameraCreator = require(script.Parent)
|
|
|
|
local CFrame_new = CFrame.new
|
|
local Vector2_new = Vector2.new
|
|
local Vector3_new = Vector3.new
|
|
local math_min = math.min
|
|
local math_max = math.max
|
|
local math_atan2 = math.atan2
|
|
local math_rad = math.rad
|
|
local math_abs = math.abs
|
|
|
|
local HUMANOIDSTATE_CLIMBING = Enum.HumanoidStateType.Climbing
|
|
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
|
local UP_VECTOR = Vector3.new(0, 1, 0)
|
|
local XZ_VECTOR = Vector3.new(1, 0, 1)
|
|
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
|
|
local PORTRAIT_OFFSET = Vector3.new(0, -3, 0)
|
|
|
|
local function clamp(low, high, num)
|
|
return num > high and high or num < low and low or num
|
|
end
|
|
|
|
local function IsFinite(num)
|
|
return num == num and num ~= 1/0 and num ~= -1/0
|
|
end
|
|
|
|
local function IsFiniteVector3(vec3)
|
|
return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
|
|
end
|
|
|
|
-- May return NaN or inf or -inf
|
|
local function findAngleBetweenXZVectors(vec2, vec1)
|
|
-- This is a way of finding the angle between the two vectors:
|
|
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
|
|
end
|
|
|
|
local function CreateFollowCamera()
|
|
local module = RootCameraCreator()
|
|
|
|
local tweenAcceleration = math_rad(220)
|
|
local tweenSpeed = math_rad(0)
|
|
local tweenMaxSpeed = math_rad(250)
|
|
local timeBeforeAutoRotate = 2
|
|
|
|
local lastUpdate = tick()
|
|
module.LastUserPanCamera = tick()
|
|
function module:Update()
|
|
module:ProcessTweens()
|
|
local now = tick()
|
|
local timeDelta = (now - lastUpdate)
|
|
|
|
local userPanningTheCamera = (self.UserPanningTheCamera == true)
|
|
local camera = workspace.CurrentCamera
|
|
local player = PlayersService.LocalPlayer
|
|
local humanoid = self:GetHumanoid()
|
|
local cameraSubject = camera and camera.CameraSubject
|
|
local isClimbing = humanoid and humanoid:GetState() == HUMANOIDSTATE_CLIMBING
|
|
local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
|
|
local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
|
|
|
|
if lastUpdate == nil or now - lastUpdate > 1 then
|
|
module:ResetCameraLook()
|
|
self.LastCameraTransform = nil
|
|
end
|
|
|
|
if lastUpdate then
|
|
|
|
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, now - lastUpdate)
|
|
local angle = 0
|
|
-- NOTE: Traditional follow camera does not rotate with arrow keys
|
|
if not (isInVehicle or isOnASkateboard) then
|
|
angle = angle + (self.TurningLeft and -120 or 0)
|
|
angle = angle + (self.TurningRight and 120 or 0)
|
|
end
|
|
|
|
local gamepadRotation = self:UpdateGamepad()
|
|
if gamepadRotation ~= Vector2.new(0,0) then
|
|
userPanningTheCamera = true
|
|
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
|
end
|
|
|
|
if angle ~= 0 then
|
|
userPanningTheCamera = true
|
|
self.RotateInput = self.RotateInput + Vector2_new(math_rad(angle * delta), 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Reset tween speed if user is panning
|
|
if userPanningTheCamera then
|
|
tweenSpeed = 0
|
|
module.LastUserPanCamera = tick()
|
|
end
|
|
|
|
local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
|
|
|
|
local subjectPosition = self:GetSubjectPosition()
|
|
if subjectPosition and player and camera then
|
|
local zoom = self:GetCameraZoom()
|
|
if zoom < 0.5 then
|
|
zoom = 0.5
|
|
end
|
|
|
|
if self:GetShiftLock() and not self:IsInFirstPerson() then
|
|
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
|
local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
|
|
if IsFiniteVector3(offset) then
|
|
subjectPosition = subjectPosition + offset
|
|
end
|
|
else
|
|
if self.LastCameraTransform and not userPanningTheCamera then
|
|
local isInFirstPerson = self:IsInFirstPerson()
|
|
if (isClimbing or isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
|
|
if isInFirstPerson then
|
|
if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
|
|
local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
|
|
if 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 = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
|
|
|
|
local percent = clamp(0, 1, tweenSpeed*timeDelta)
|
|
if not isClimbing and self:IsInFirstPerson() then
|
|
percent = 1
|
|
end
|
|
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
|
|
-- Check for NaN
|
|
if IsFinite(y) and math_abs(y) > 0.0001 then
|
|
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
|
|
end
|
|
end
|
|
elseif not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled then
|
|
local lastVec = -(self.LastCameraTransform.p - subjectPosition)
|
|
|
|
local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
|
|
|
|
-- 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 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
|
|
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
|
self.RotateInput = ZERO_VECTOR2
|
|
|
|
if VRService.VREnabled then
|
|
camera.Focus = self:GetVRFocus(subjectPosition, timeDelta)
|
|
elseif self:IsPortraitMode() then
|
|
camera.Focus = CFrame_new(subjectPosition + PORTRAIT_OFFSET)
|
|
else
|
|
camera.Focus = CFrame_new(subjectPosition)
|
|
end
|
|
camera.CFrame = CFrame_new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p) + Vector3.new(0, self:GetCameraHeight(), 0)
|
|
|
|
self.LastCameraTransform = camera.CFrame
|
|
self.LastCameraFocus = camera.Focus
|
|
if isInVehicle or isOnASkateboard and cameraSubject:IsA('BasePart') then
|
|
self.LastSubjectCFrame = cameraSubject.CFrame
|
|
else
|
|
self.LastSubjectCFrame = nil
|
|
end
|
|
end
|
|
|
|
lastUpdate = now
|
|
end
|
|
|
|
return module
|
|
end
|
|
|
|
return CreateFollowCamera
|