add gs
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"lint": {
|
||||
"LocalShadow": "fatal",
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
local PlayersService = game:GetService('Players')
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
local XZ_VECTOR = Vector3.new(1,0,1)
|
||||
|
||||
local Vector2_new = Vector2.new
|
||||
local CFrame_new = CFrame.new
|
||||
local math_atan2 = math.atan2
|
||||
local math_min = math.min
|
||||
|
||||
local function IsFinite(num)
|
||||
return num == num and num ~= 1/0 and num ~= -1/0
|
||||
end
|
||||
|
||||
-- May return NaN or inf or -inf
|
||||
-- This is a way of finding the angle between the two vectors:
|
||||
local function findAngleBetweenXZVectors(vec2, vec1)
|
||||
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
|
||||
end
|
||||
|
||||
local function CreateAttachCamera()
|
||||
local module = RootCameraCreator()
|
||||
|
||||
local lastUpdate = tick()
|
||||
function module:Update()
|
||||
local now = tick()
|
||||
|
||||
local camera = workspace.CurrentCamera
|
||||
|
||||
if lastUpdate == nil or now - lastUpdate > 1 then
|
||||
module:ResetCameraLook()
|
||||
self.LastCameraTransform = nil
|
||||
end
|
||||
|
||||
local subjectPosition = self:GetSubjectPosition()
|
||||
if subjectPosition and camera then
|
||||
local zoom = self:GetCameraZoom()
|
||||
if zoom <= 0 then
|
||||
zoom = 0.1
|
||||
end
|
||||
|
||||
|
||||
local humanoid = self:GetHumanoid()
|
||||
if lastUpdate and humanoid and humanoid.Torso then
|
||||
|
||||
-- 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 gamepadRotation = self:UpdateGamepad()
|
||||
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
||||
|
||||
local forwardVector = humanoid.Torso.CFrame.lookVector
|
||||
|
||||
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
|
||||
if IsFinite(y) then
|
||||
-- Preserve vertical rotation from user input
|
||||
self.RotateInput = Vector2_new(y, self.RotateInput.Y)
|
||||
end
|
||||
end
|
||||
|
||||
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
|
||||
camera.Focus = CFrame_new(subjectPosition)
|
||||
local newCFrame = CFrame_new(subjectPosition - (zoom * newLookVector), subjectPosition)
|
||||
camera.CFrame = newCFrame
|
||||
self.LastCameraTransform = newCFrame
|
||||
end
|
||||
lastUpdate = now
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateAttachCamera
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
|
||||
local PlayersService = game:GetService('Players')
|
||||
local VRService = game:GetService("VRService")
|
||||
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local UP_VECTOR = Vector3.new(0, 1, 0)
|
||||
local XZ_VECTOR = Vector3.new(1, 0, 1)
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
|
||||
local VR_PITCH_FRACTION = 0.25
|
||||
|
||||
local Vector3_new = Vector3.new
|
||||
local CFrame_new = CFrame.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 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
|
||||
-- This is a way of finding the angle between the two vectors:
|
||||
local function findAngleBetweenXZVectors(vec2, vec1)
|
||||
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
|
||||
end
|
||||
|
||||
local function CreateClassicCamera()
|
||||
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 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
|
||||
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, now - lastUpdate)
|
||||
|
||||
if gamepadRotation ~= ZERO_VECTOR2 then
|
||||
userPanningTheCamera = true
|
||||
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
||||
end
|
||||
|
||||
local angle = 0
|
||||
if not (isInVehicle or isOnASkateboard) then
|
||||
angle = angle + (self.TurningLeft and -120 or 0)
|
||||
angle = angle + (self.TurningRight and 120 or 0)
|
||||
end
|
||||
|
||||
if angle ~= 0 then
|
||||
self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
|
||||
userPanningTheCamera = true
|
||||
end
|
||||
end
|
||||
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
|
||||
-- We need to use the right vector of the camera after rotation, not before
|
||||
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 not userPanningTheCamera and self.LastCameraTransform then
|
||||
local isInFirstPerson = self:IsInFirstPerson()
|
||||
if (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 self:IsInFirstPerson() then
|
||||
percent = 1
|
||||
end
|
||||
|
||||
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
|
||||
if IsFinite(y) and math_abs(y) > 0.0001 then
|
||||
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local VREnabled = VRService.VREnabled
|
||||
camera.Focus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame_new(subjectPosition)
|
||||
|
||||
local cameraFocusP = camera.Focus.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:RotateCamera(vecToSubject.unit * XZ_VECTOR, Vector2.new(self.RotateInput.x, 0)) * 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
|
||||
|
||||
camera.CFrame = CFrame_new(newPos, lookAt) + Vector3_new(0, cameraHeight, 0)
|
||||
end
|
||||
else
|
||||
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
camera.CFrame = CFrame_new(cameraFocusP - (zoom * newLookVector), cameraFocusP)
|
||||
end
|
||||
|
||||
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 CreateClassicCamera
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
local PlayersService = game:GetService('Players')
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
|
||||
local CFrame_new = CFrame.new
|
||||
local math_min = math.min
|
||||
|
||||
local function CreateFixedCamera()
|
||||
local module = RootCameraCreator()
|
||||
|
||||
local lastUpdate = tick()
|
||||
function module:Update()
|
||||
local now = tick()
|
||||
|
||||
local camera = workspace.CurrentCamera
|
||||
local player = PlayersService.LocalPlayer
|
||||
if lastUpdate == nil or now - lastUpdate > 1 then
|
||||
module:ResetCameraLook()
|
||||
self.LastCameraTransform = nil
|
||||
end
|
||||
|
||||
if lastUpdate then
|
||||
-- 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 gamepadRotation = self:UpdateGamepad()
|
||||
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
||||
end
|
||||
|
||||
local subjectPosition = self:GetSubjectPosition()
|
||||
if subjectPosition and player and camera then
|
||||
local zoom = self:GetCameraZoom()
|
||||
if zoom <= 0 then
|
||||
zoom = 0.1
|
||||
end
|
||||
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
|
||||
camera.CFrame = CFrame_new(camera.CFrame.p, camera.CFrame.p + (zoom * newLookVector))
|
||||
self.LastCameraTransform = camera.CFrame
|
||||
end
|
||||
lastUpdate = now
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateFixedCamera
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
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
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
--[[
|
||||
Orbital Camera 1.0.0
|
||||
AllYourBlox
|
||||
|
||||
Derived from ClassicCamera, adds camera angle constraints, and represents position values in spherical
|
||||
coordinates (azimuth, elevation, radius), instead of Cartesian coordinates (x, y, z). Azimuth is the
|
||||
angle of rotation about the Y axis, with the zero-angle reference point corresponding to offsetting
|
||||
the camera in the +Z direction, from where it will be looking in the -Z direction by default. Elevation
|
||||
is the angle up from the XZ plane, where zero degrees is on the plane, and +90 degrees is on the +Y
|
||||
axis. Distance is the camera-to-subject distance, the spherical coordinates radius, specified in studs.
|
||||
--]]
|
||||
|
||||
-- Do not edit these values, they are not the developer-set limits, they are limits
|
||||
-- to the values the camera system equations can correctly handle
|
||||
local MIN_ALLOWED_ELEVATION_DEG = -80
|
||||
local MAX_ALLOWED_ELEVATION_DEG = 80
|
||||
|
||||
local externalProperties = {}
|
||||
externalProperties["InitialDistance"] = 25
|
||||
externalProperties["MinDistance"] = 10
|
||||
externalProperties["MaxDistance"] = 100
|
||||
externalProperties["InitialElevation"] = 35
|
||||
externalProperties["MinElevation"] = 35
|
||||
externalProperties["MaxElevation"] = 35
|
||||
externalProperties["ReferenceAzimuth"] = -45 -- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
|
||||
externalProperties["CWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
|
||||
externalProperties["CCWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
|
||||
externalProperties["UseAzimuthLimits"] = false -- Full rotation around Y axis available by default
|
||||
|
||||
local curAzimuthRad
|
||||
local minAzimuthAbsoluteRad
|
||||
local maxAzimuthAbsoluteRad
|
||||
local useAzimuthLimits
|
||||
local curElevationRad
|
||||
local minElevationRad
|
||||
local maxElevationRad
|
||||
local curDistance
|
||||
local minDistance
|
||||
local maxDistance
|
||||
|
||||
local UNIT_Z = Vector3.new(0,0,1)
|
||||
local TAU = 2 * math.pi
|
||||
|
||||
local changedSignalConnections = {}
|
||||
|
||||
-- End of OrbitalCamera additions
|
||||
local PlayersService = game:GetService('Players')
|
||||
local VRService = game:GetService("VRService")
|
||||
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local UP_VECTOR = Vector3.new(0, 1, 0)
|
||||
local XZ_VECTOR = Vector3.new(1, 0, 1)
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
|
||||
local VR_PITCH_FRACTION = 0.25
|
||||
|
||||
local Vector3_new = Vector3.new
|
||||
local CFrame_new = CFrame.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
|
||||
|
||||
--[[ Gamepad Support ]]--
|
||||
local THUMBSTICK_DEADZONE = 0.2
|
||||
local r3ButtonDown = false
|
||||
local l3ButtonDown = false
|
||||
local currentZoomSpeed = 1 -- Multiplier, so 1 == no zooming
|
||||
|
||||
local function clamp(value, minValue, maxValue)
|
||||
if maxValue < minValue then
|
||||
maxValue = minValue
|
||||
end
|
||||
return math.clamp(value, minValue, maxValue)
|
||||
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
|
||||
-- This is a way of finding the angle between the two vectors:
|
||||
local function findAngleBetweenXZVectors(vec2, vec1)
|
||||
return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
|
||||
end
|
||||
|
||||
local function GetValueObject(name, defaultValue)
|
||||
local valueObj = script:FindFirstChild(name)
|
||||
if valueObj then
|
||||
return valueObj.Value
|
||||
end
|
||||
return defaultValue
|
||||
end
|
||||
|
||||
local function LoadOrCreateNumberValueParameter(name, valueType, updateFunction)
|
||||
local valueObj = script:FindFirstChild(name)
|
||||
|
||||
if valueObj and valueObj:isA(valueType) then
|
||||
-- Value object exists and is the correct type, use its value
|
||||
externalProperties[name] = valueObj.Value
|
||||
elseif externalProperties[name] ~= nil then
|
||||
-- Create missing (or replace incorrectly-typed) valueObject with default value
|
||||
valueObj = Instance.new(valueType)
|
||||
valueObj.Name = name
|
||||
valueObj.Parent = script
|
||||
valueObj.Value = externalProperties[name]
|
||||
else
|
||||
print("externalProperties table has no entry for ",name)
|
||||
return
|
||||
end
|
||||
|
||||
if updateFunction then
|
||||
if changedSignalConnections[name] then
|
||||
changedSignalConnections[name]:disconnect()
|
||||
end
|
||||
changedSignalConnections[name] = valueObj.Changed:connect(function(newValue)
|
||||
externalProperties[name] = newValue
|
||||
updateFunction()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetAndBoundsCheckAzimuthValues()
|
||||
minAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) - math.abs(math.rad(externalProperties["CWAzimuthTravel"]))
|
||||
maxAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) + math.abs(math.rad(externalProperties["CCWAzimuthTravel"]))
|
||||
useAzimuthLimits = externalProperties["UseAzimuthLimits"]
|
||||
if useAzimuthLimits then
|
||||
curAzimuthRad = math.max(curAzimuthRad, minAzimuthAbsoluteRad)
|
||||
curAzimuthRad = math.min(curAzimuthRad, maxAzimuthAbsoluteRad)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetAndBoundsCheckElevationValues()
|
||||
-- These degree values are the direct user input values. It is deliberate that they are
|
||||
-- ranged checked only against the extremes, and not against each other. Any time one
|
||||
-- is changed, both of the internal values in radians are recalculated. This allows for
|
||||
-- A developer to change the values in any order and for the end results to be that the
|
||||
-- internal values adjust to match intent as best as possible.
|
||||
local minElevationDeg = math.max(externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
|
||||
local maxElevationDeg = math.min(externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
|
||||
|
||||
-- Set internal values in radians
|
||||
minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
|
||||
maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
|
||||
curElevationRad = math.max(curElevationRad, minElevationRad)
|
||||
curElevationRad = math.min(curElevationRad, maxElevationRad)
|
||||
end
|
||||
|
||||
local function SetAndBoundsCheckDistanceValues()
|
||||
minDistance = externalProperties["MinDistance"]
|
||||
maxDistance = externalProperties["MaxDistance"]
|
||||
curDistance = math.max(curDistance, minDistance)
|
||||
curDistance = math.min(curDistance, maxDistance)
|
||||
end
|
||||
|
||||
-- This loads from, or lazily creates, NumberValue objects for exposed parameters
|
||||
local function LoadNumberValueParameters()
|
||||
-- These initial values do not require change listeners since they are read only once
|
||||
LoadOrCreateNumberValueParameter("InitialElevation", "NumberValue", nil)
|
||||
LoadOrCreateNumberValueParameter("InitialDistance", "NumberValue", nil)
|
||||
|
||||
-- Note: ReferenceAzimuth is also used as an initial value, but needs a change listener because it is used in the calculation of the limits
|
||||
LoadOrCreateNumberValueParameter("ReferenceAzimuth", "NumberValue", SetAndBoundsCheckAzimuthValues)
|
||||
LoadOrCreateNumberValueParameter("CWAzimuthTravel", "NumberValue", SetAndBoundsCheckAzimuthValues)
|
||||
LoadOrCreateNumberValueParameter("CCWAzimuthTravel", "NumberValue", SetAndBoundsCheckAzimuthValues)
|
||||
LoadOrCreateNumberValueParameter("MinElevation", "NumberValue", SetAndBoundsCheckElevationValues)
|
||||
LoadOrCreateNumberValueParameter("MaxElevation", "NumberValue", SetAndBoundsCheckElevationValues)
|
||||
LoadOrCreateNumberValueParameter("MinDistance", "NumberValue", SetAndBoundsCheckDistanceValues)
|
||||
LoadOrCreateNumberValueParameter("MaxDistance", "NumberValue", SetAndBoundsCheckDistanceValues)
|
||||
LoadOrCreateNumberValueParameter("UseAzimuthLimits", "BoolValue", SetAndBoundsCheckAzimuthValues)
|
||||
|
||||
-- Internal values set (in radians, from degrees), plus sanitization
|
||||
curAzimuthRad = math.rad(externalProperties["ReferenceAzimuth"])
|
||||
curElevationRad = math.rad(externalProperties["InitialElevation"])
|
||||
curDistance = externalProperties["InitialDistance"]
|
||||
|
||||
SetAndBoundsCheckAzimuthValues()
|
||||
SetAndBoundsCheckElevationValues()
|
||||
SetAndBoundsCheckDistanceValues()
|
||||
end
|
||||
|
||||
local function CreateOrbitalCamera()
|
||||
local module = RootCameraCreator()
|
||||
|
||||
LoadNumberValueParameters()
|
||||
|
||||
module.DefaultZoom = curDistance
|
||||
|
||||
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 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
|
||||
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, now - lastUpdate)
|
||||
|
||||
if gamepadRotation ~= ZERO_VECTOR2 then
|
||||
userPanningTheCamera = true
|
||||
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
||||
end
|
||||
|
||||
local angle = 0
|
||||
if not (isInVehicle or isOnASkateboard) then
|
||||
angle = angle + (self.TurningLeft and -120 or 0)
|
||||
angle = angle + (self.TurningRight and 120 or 0)
|
||||
end
|
||||
|
||||
if angle ~= 0 then
|
||||
self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
|
||||
userPanningTheCamera = true
|
||||
end
|
||||
end
|
||||
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:ZoomCamera(curDistance * currentZoomSpeed)
|
||||
|
||||
|
||||
if not userPanningTheCamera and self.LastCameraTransform then
|
||||
local isInFirstPerson = self:IsInFirstPerson()
|
||||
if (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 self:IsInFirstPerson() then
|
||||
percent = 1
|
||||
end
|
||||
|
||||
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
|
||||
if IsFinite(y) and math_abs(y) > 0.0001 then
|
||||
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local VREnabled = VRService.VREnabled
|
||||
camera.Focus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame_new(subjectPosition)
|
||||
|
||||
local cameraFocusP = camera.Focus.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:RotateCamera(vecToSubject.unit * XZ_VECTOR, Vector2.new(self.RotateInput.x, 0)) * 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
|
||||
|
||||
camera.CFrame = CFrame_new(newPos, lookAt) + Vector3_new(0, cameraHeight, 0)
|
||||
end
|
||||
else
|
||||
-- self.RotateInput is a Vector2 of mouse movement deltas since last update
|
||||
curAzimuthRad = curAzimuthRad - self.RotateInput.x
|
||||
|
||||
if useAzimuthLimits then
|
||||
curAzimuthRad = clamp(curAzimuthRad, minAzimuthAbsoluteRad, maxAzimuthAbsoluteRad)
|
||||
else
|
||||
curAzimuthRad = (curAzimuthRad ~= 0) and (math.sign(curAzimuthRad) * (math.abs(curAzimuthRad) % TAU)) or 0
|
||||
end
|
||||
|
||||
curDistance = clamp(zoom, minDistance, maxDistance)
|
||||
|
||||
curElevationRad = clamp(curElevationRad + self.RotateInput.y, minElevationRad,maxElevationRad)
|
||||
|
||||
local cameraPosVector = curDistance * ( CFrame.fromEulerAnglesYXZ( -curElevationRad, curAzimuthRad, 0 ) * UNIT_Z )
|
||||
local camPos = subjectPosition + cameraPosVector
|
||||
|
||||
camera.CFrame = CFrame.new(camPos, subjectPosition)
|
||||
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
function module:GetCameraZoom()
|
||||
return curDistance
|
||||
end
|
||||
|
||||
function module:ZoomCamera(desiredZoom)
|
||||
local player = PlayersService.LocalPlayer
|
||||
if player then
|
||||
curDistance = clamp(desiredZoom, minDistance, maxDistance)
|
||||
end
|
||||
|
||||
local isFirstPerson = self:GetCameraZoom() < 2
|
||||
|
||||
--ShiftLockController:SetIsInFirstPerson(isFirstPerson)
|
||||
-- set mouse behavior
|
||||
self:UpdateMouseBehavior()
|
||||
return self:GetCameraZoom()
|
||||
end
|
||||
|
||||
function module:ZoomCameraBy(zoomScale)
|
||||
local newDist = curDistance
|
||||
-- Can break into more steps to get more accurate integration
|
||||
newDist = self:rk4Integrator(curDistance, zoomScale, 1)
|
||||
self:ZoomCamera(newDist)
|
||||
return self:GetCameraZoom()
|
||||
end
|
||||
|
||||
function module:ZoomCameraFixedBy(zoomIncrement)
|
||||
return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
|
||||
end
|
||||
|
||||
function module:SetInitialOrientation(humanoid)
|
||||
local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
|
||||
local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, self:GetCameraLook())
|
||||
local vertShift = math.asin(self:GetCameraLook().y) - math.asin(newDesiredLook.y)
|
||||
if not IsFinite(horizontalShift) then
|
||||
horizontalShift = 0
|
||||
end
|
||||
if not IsFinite(vertShift) then
|
||||
vertShift = 0
|
||||
end
|
||||
self.RotateInput = Vector2.new(horizontalShift, vertShift)
|
||||
end
|
||||
|
||||
function module.getGamepadPan(name, state, input)
|
||||
if input.UserInputType == module.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
|
||||
if r3ButtonDown or l3ButtonDown then
|
||||
-- R3 or L3 Thumbstick is depressed, right stick controls dolly in/out
|
||||
if (input.Position.Y > THUMBSTICK_DEADZONE) then
|
||||
currentZoomSpeed = 0.96
|
||||
elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
|
||||
currentZoomSpeed = 1.04
|
||||
else
|
||||
currentZoomSpeed = 1.00
|
||||
end
|
||||
else
|
||||
if state == Enum.UserInputState.Cancel then
|
||||
module.GamepadPanningCamera = ZERO_VECTOR2
|
||||
return
|
||||
end
|
||||
|
||||
local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
|
||||
if inputVector.magnitude > THUMBSTICK_DEADZONE then
|
||||
module.GamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
|
||||
else
|
||||
module.GamepadPanningCamera = ZERO_VECTOR2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function module.doGamepadZoom(name, state, input)
|
||||
if input.UserInputType == module.activeGamepad and (input.KeyCode == Enum.KeyCode.ButtonR3 or input.KeyCode == Enum.KeyCode.ButtonL3) then
|
||||
if (state == Enum.UserInputState.Begin) then
|
||||
r3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonR3
|
||||
l3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonL3
|
||||
elseif (state == Enum.UserInputState.End) then
|
||||
if (input.KeyCode == Enum.KeyCode.ButtonR3) then
|
||||
r3ButtonDown = false
|
||||
elseif (input.KeyCode == Enum.KeyCode.ButtonL3) then
|
||||
l3ButtonDown = false
|
||||
end
|
||||
if (not r3ButtonDown) and (not l3ButtonDown) then
|
||||
currentZoomSpeed = 1.00
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function module:BindGamepadInputActions()
|
||||
local ContextActionService = game:GetService('ContextActionService')
|
||||
ContextActionService:BindAction("OrbitalCamGamepadPan", module.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
|
||||
ContextActionService:BindAction("OrbitalCamGamepadZoom", module.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
|
||||
ContextActionService:BindAction("OrbitalCamGamepadZoomAlt", module.doGamepadZoom, false, Enum.KeyCode.ButtonL3)
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateOrbitalCamera
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local function CreateScriptableCamera()
|
||||
local module = RootCameraCreator()
|
||||
|
||||
function module:Update()
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateScriptableCamera
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
local PlayersService = game:GetService('Players')
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
|
||||
local CFrame_new = CFrame.new
|
||||
local math_min = math.min
|
||||
|
||||
local function CreateTrackCamera()
|
||||
local module = RootCameraCreator()
|
||||
|
||||
local lastUpdate = tick()
|
||||
function module:Update()
|
||||
local now = tick()
|
||||
|
||||
local userPanningTheCamera = (self.UserPanningTheCamera == true)
|
||||
local camera = workspace.CurrentCamera
|
||||
local player = PlayersService.LocalPlayer
|
||||
|
||||
if lastUpdate == nil or now - lastUpdate > 1 then
|
||||
module:ResetCameraLook()
|
||||
self.LastCameraTransform = nil
|
||||
end
|
||||
|
||||
if lastUpdate then
|
||||
-- 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 gamepadRotation = self:UpdateGamepad()
|
||||
if gamepadRotation ~= ZERO_VECTOR2 then
|
||||
userPanningTheCamera = true
|
||||
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
|
||||
end
|
||||
end
|
||||
|
||||
local subjectPosition = self:GetSubjectPosition()
|
||||
if subjectPosition and player and camera then
|
||||
local zoom = self:GetCameraZoom()
|
||||
if zoom <= 0 then
|
||||
zoom = 0.1
|
||||
end
|
||||
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
|
||||
camera.Focus = CFrame_new(subjectPosition)
|
||||
camera.CFrame = CFrame_new(subjectPosition - (zoom * newLookVector), subjectPosition)
|
||||
self.LastCameraTransform = camera.CoordinateFrame
|
||||
end
|
||||
lastUpdate = now
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateTrackCamera
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
local PlayersService = game:GetService('Players')
|
||||
local UserInputService = game:GetService('UserInputService')
|
||||
local VRService = game:GetService("VRService")
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local XZ_VECTOR = Vector3.new(1, 0, 1)
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
|
||||
local PRESNAP_TIME_OFFSET = 0.5 --seconds
|
||||
local PRESNAP_TIME_LATCH = PRESNAP_TIME_OFFSET * 10
|
||||
|
||||
local HEIGHT_OFFSET = 3
|
||||
local HORZ_OFFSET = 4
|
||||
|
||||
local function CreateVRCamera()
|
||||
local module = RootCameraCreator()
|
||||
module.AllowOcclusion = false
|
||||
|
||||
local lastUpdate = tick()
|
||||
|
||||
local forceSnap = true
|
||||
local forceSnapPoint = nil
|
||||
local lastLook = Vector3.new(0, 0, -1)
|
||||
|
||||
local movementUpdateEventConn = nil
|
||||
|
||||
local lastSnapTimeEstimate = math.huge
|
||||
local snapId = 0
|
||||
local waitingForSnap = false
|
||||
local snappedHeadOffset = VRService:GetUserCFrame(Enum.UserCFrame.Head).p
|
||||
local snapPendingWhileJumping = false
|
||||
local isJumping = false
|
||||
local autoSnapsPaused = false
|
||||
|
||||
local currentDestination = nil
|
||||
|
||||
local function forceSnapTo(snapPoint)
|
||||
forceSnap = true
|
||||
forceSnapPoint = snapPoint
|
||||
snapId = snapId + 1
|
||||
waitingForSnap = false
|
||||
end
|
||||
|
||||
local function onMovementUpdateEvent(updateType, arg1, arg2)
|
||||
if updateType == "targetPoint" then
|
||||
currentDestination = arg1
|
||||
end
|
||||
if updateType == "timing" then
|
||||
local estimatedTimeRemaining = arg1
|
||||
local snapPoint = arg2
|
||||
|
||||
if waitingForSnap and estimatedTimeRemaining > lastSnapTimeEstimate then
|
||||
--our estimate grew, so cancel this snap and potentially re-evaluate it
|
||||
waitingForSnap = false
|
||||
snapId = snapId + 1
|
||||
end
|
||||
|
||||
if estimatedTimeRemaining < PRESNAP_TIME_LATCH and estimatedTimeRemaining > PRESNAP_TIME_OFFSET then
|
||||
waitingForSnap = true
|
||||
snapId = snapId + 1
|
||||
local thisSnapId = snapId
|
||||
|
||||
local timeToWait = estimatedTimeRemaining - PRESNAP_TIME_OFFSET
|
||||
coroutine.wrap(function()
|
||||
wait(timeToWait)
|
||||
if waitingForSnap and snapId == thisSnapId then
|
||||
waitingForSnap = false
|
||||
forceSnap = true
|
||||
forceSnapPoint = snapPoint
|
||||
end
|
||||
end)()
|
||||
end
|
||||
elseif updateType == "shortPath" or updateType == "pathFailure" or updateType == "offtrack" and not autoSnapsPaused then
|
||||
if isJumping then
|
||||
snapPendingWhileJumping = true
|
||||
return
|
||||
end
|
||||
local snapPoint = arg1
|
||||
forceSnapTo(snapPoint)
|
||||
elseif updateType == "force" then
|
||||
snapPendingWhileJumping = false
|
||||
isJumping = false
|
||||
forceSnapTo(nil)
|
||||
end
|
||||
end
|
||||
|
||||
local function onResetCameraAction(actionName, inputState, inputObj)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
autoSnapsPaused = true
|
||||
onMovementUpdateEvent("force", nil, nil)
|
||||
else
|
||||
autoSnapsPaused = false
|
||||
end
|
||||
end
|
||||
|
||||
local function bindActions(bind)
|
||||
if bind then
|
||||
ContextActionService:BindActionAtPriority("ResetCameraVR", onResetCameraAction, false, Enum.ContextActionPriority.Low.Value, Enum.KeyCode.ButtonL2)
|
||||
else
|
||||
autoSnapsPaused = false
|
||||
ContextActionService:UnbindAction("ResetCameraVR")
|
||||
end
|
||||
end
|
||||
|
||||
local lastKnownTorsoPosition = Vector3.new()
|
||||
local function onCharacterAdded(character)
|
||||
local humanoid = character:WaitForChild("Humanoid")
|
||||
|
||||
humanoid.StateChanged:connect(function(oldState, newState)
|
||||
local camera = workspace.CurrentCamera
|
||||
if not camera or camera.CameraSubject ~= humanoid or not VRService.VREnabled then
|
||||
return
|
||||
end
|
||||
if newState == Enum.HumanoidStateType.Jumping then
|
||||
isJumping = true
|
||||
elseif newState == Enum.HumanoidStateType.Landed then
|
||||
if snapPendingWhileJumping then
|
||||
forceSnapTo(nil)
|
||||
snapPendingWhileJumping = false
|
||||
end
|
||||
isJumping = false
|
||||
elseif newState == Enum.HumanoidStateType.Dead then
|
||||
forceSnapTo(nil)
|
||||
elseif newState == Enum.HumanoidStateType.Swimming then
|
||||
if isJumping and snapPendingWhileJumping then
|
||||
--Jumped into water and let go of controls during flight, treat as a normal landing
|
||||
forceSnapTo(nil)
|
||||
snapPendingWhileJumping = false
|
||||
end
|
||||
isJumping = false
|
||||
end
|
||||
end)
|
||||
|
||||
local humanoidRootPart = humanoid.Torso
|
||||
humanoidRootPart.Changed:connect(function(property)
|
||||
local camera = workspace.CurrentCamera
|
||||
local cameraSubject = camera.CameraSubject
|
||||
if camera and cameraSubject == humanoid and property == "CFrame" or property == "Position" then
|
||||
if (humanoidRootPart.Position - lastKnownTorsoPosition).magnitude > 5 then
|
||||
forceSnapTo(nil)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if LocalPlayer.Character then
|
||||
onCharacterAdded(LocalPlayer.Character)
|
||||
end
|
||||
LocalPlayer.CharacterAdded:connect(onCharacterAdded)
|
||||
|
||||
spawn(function()
|
||||
local rootCamera = script.Parent
|
||||
if not rootCamera then return end
|
||||
|
||||
local cameraScript = rootCamera.Parent
|
||||
if not cameraScript then return end
|
||||
|
||||
local playerScripts = cameraScript.Parent
|
||||
if not playerScripts then return end
|
||||
|
||||
local controlScript = playerScripts:WaitForChild("ControlScript")
|
||||
local masterControlModule = controlScript:WaitForChild("MasterControl")
|
||||
local vrNavigationModule = masterControlModule:WaitForChild("VRNavigation")
|
||||
local movementUpdateEvent = vrNavigationModule:WaitForChild("MovementUpdate")
|
||||
|
||||
movementUpdateEventConn = movementUpdateEvent.Event:connect(onMovementUpdateEvent)
|
||||
end)
|
||||
|
||||
local onCameraSubjectChangedConn = nil
|
||||
local function onCameraSubjectChanged()
|
||||
local camera = workspace.CurrentCamera
|
||||
if camera and camera.CameraSubject then
|
||||
delay(1, function () forceSnap = true end)
|
||||
end
|
||||
end
|
||||
|
||||
local function onCurrentCameraChanged()
|
||||
if onCameraSubjectChangedConn then
|
||||
onCameraSubjectChangedConn:disconnect()
|
||||
onCameraSubjectChangedConn = nil
|
||||
end
|
||||
if workspace.CurrentCamera then
|
||||
onCameraSubjectChangedConn = workspace.CurrentCamera:GetPropertyChangedSignal("CameraSubject"):connect(onCameraSubjectChanged)
|
||||
onCameraSubjectChanged()
|
||||
end
|
||||
end
|
||||
workspace:GetPropertyChangedSignal("CurrentCamera"):connect(onCurrentCameraChanged)
|
||||
onCurrentCameraChanged()
|
||||
|
||||
local function onVREnabled()
|
||||
bindActions(VRService.VREnabled)
|
||||
end
|
||||
VRService:GetPropertyChangedSignal("VREnabled"):connect(onVREnabled)
|
||||
|
||||
function module:Update()
|
||||
local now = tick()
|
||||
local timeDelta = now - lastUpdate
|
||||
|
||||
local camera = workspace.CurrentCamera
|
||||
local cameraSubject = camera and camera.CameraSubject
|
||||
local player = PlayersService.LocalPlayer
|
||||
local subjectPosition = self:GetSubjectPosition()
|
||||
local zoom = self:GetCameraZoom()
|
||||
|
||||
local subjectPosition = self:GetSubjectPosition()
|
||||
local gamepadRotation = self:UpdateGamepad()
|
||||
|
||||
if subjectPosition and currentDestination then
|
||||
local dist = (currentDestination - subjectPosition).magnitude
|
||||
if dist < 10 then
|
||||
forceSnap = true
|
||||
forceSnapPoint = currentDestination
|
||||
currentDestination = nil
|
||||
end
|
||||
end
|
||||
|
||||
if cameraSubject and cameraSubject:IsA("Humanoid") then
|
||||
local rootPart = cameraSubject.RootPart
|
||||
if rootPart then
|
||||
lastKnownTorsoPosition = rootPart.Position
|
||||
end
|
||||
end
|
||||
|
||||
local look = lastLook
|
||||
if subjectPosition and forceSnap then
|
||||
forceSnap = false
|
||||
|
||||
local newFocusPoint = subjectPosition
|
||||
if forceSnapPoint then
|
||||
newFocusPoint = Vector3.new(forceSnapPoint.X, subjectPosition.Y, forceSnapPoint.Z)
|
||||
end
|
||||
|
||||
camera.Focus = CFrame.new(newFocusPoint)
|
||||
forceSnapPoint = nil
|
||||
look = camera:GetRenderCFrame().lookVector
|
||||
snappedHeadOffset = VRService:GetUserCFrame(Enum.UserCFrame.Head).p
|
||||
end
|
||||
if subjectPosition and player and camera then
|
||||
local cameraFocusP = camera.Focus.p
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
look = (look * XZ_VECTOR).unit
|
||||
camera.CFrame = CFrame.new(cameraFocusP - (HORZ_OFFSET * look)) + Vector3.new(0, HEIGHT_OFFSET, 0) - snappedHeadOffset
|
||||
|
||||
self.LastCameraTransform = camera.CFrame
|
||||
self.LastCameraFocus = camera.Focus
|
||||
end
|
||||
lastLook = look
|
||||
|
||||
lastUpdate = now
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateVRCamera
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
local PlayersService = game:GetService('Players')
|
||||
local RootCameraCreator = require(script.Parent)
|
||||
|
||||
local ZERO_VECTOR2 = Vector2.new(0, 0)
|
||||
|
||||
local CFrame_new = CFrame.new
|
||||
|
||||
local function CreateWatchCamera()
|
||||
local module = RootCameraCreator()
|
||||
module.PanEnabled = false
|
||||
|
||||
local lastUpdate = tick()
|
||||
function module:Update()
|
||||
local now = tick()
|
||||
|
||||
local camera = workspace.CurrentCamera
|
||||
local player = PlayersService.LocalPlayer
|
||||
|
||||
if lastUpdate == nil or now - lastUpdate > 1 then
|
||||
module:ResetCameraLook()
|
||||
self.LastZoom = nil
|
||||
end
|
||||
|
||||
|
||||
local subjectPosition = self:GetSubjectPosition()
|
||||
if subjectPosition and player and camera then
|
||||
local cameraLook = nil
|
||||
|
||||
local humanoid = self:GetHumanoid()
|
||||
if humanoid and humanoid.Torso then
|
||||
-- TODO: let the paging buttons move the camera but not the mouse/touch
|
||||
-- currently neither do
|
||||
local diffVector = subjectPosition - camera.CFrame.p
|
||||
cameraLook = diffVector.unit
|
||||
|
||||
if self.LastZoom and self.LastZoom == self:GetCameraZoom() then
|
||||
-- Don't clobber the zoom if they zoomed the camera
|
||||
local zoom = diffVector.magnitude
|
||||
self:ZoomCamera(zoom)
|
||||
end
|
||||
end
|
||||
|
||||
local zoom = self:GetCameraZoom()
|
||||
if zoom <= 0 then
|
||||
zoom = 0.1
|
||||
end
|
||||
|
||||
local newLookVector = self:RotateVector(cameraLook or self:GetCameraLook(), self.RotateInput)
|
||||
self.RotateInput = ZERO_VECTOR2
|
||||
local newFocus = CFrame_new(subjectPosition)
|
||||
local newCamCFrame = CFrame_new(newFocus.p - (zoom * newLookVector), subjectPosition)
|
||||
|
||||
camera.Focus = newFocus
|
||||
camera.CFrame = newCamCFrame
|
||||
self.LastZoom = zoom
|
||||
end
|
||||
lastUpdate = now
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
return CreateWatchCamera
|
||||
Reference in New Issue
Block a user