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

549 lines
21 KiB
Lua

--[[
CameraScript - This module manages the selection of the current camera control module,
character occlusion module, and transparency controller. This script binds to
RenderStepped at Camera priority and calls the Update() methods on the active camera
control module, character occlusion module, and character transparency controller.
Camera control modules are instantiated and activated as-needed, they are no longer all
instantiated up front.
2018 PlayerScripts Update - AllYourBlox
--]]
local CameraScript = {}
-- NOTICE: Player property names do not all match their StarterPlayer equivalents,
-- with the differences noted in the comments on the right
local PLAYER_CAMERA_PROPERTIES =
{
"CameraMinZoomDistance",
"CameraMaxZoomDistance",
"CameraMode",
"DevCameraOcclusionMode",
"DevComputerCameraMode", -- Corresponds to StarterPlayer.DevComputerCameraMovementMode
"DevTouchCameraMode", -- Corresponds to StarterPlayer.DevTouchCameraMovementMode
-- Character movement mode
"DevComputerMovementMode",
"DevTouchMovementMode",
"DevEnableMouseLock", -- Corresponds to StarterPlayer.EnableMouseLockOption
}
local USER_GAME_SETTINGS_PROPERTIES =
{
"ComputerCameraMovementMode",
"ComputerMovementMode",
"ControlMode",
"GamepadCameraSensitivity",
"MouseSensitivity",
"RotationType",
"TouchCameraMovementMode",
"TouchMovementMode",
}
--[[ Roblox Services ]]--
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local StarterPlayer = game:GetService("StarterPlayer")
local UserGameSettings = UserSettings():GetService("UserGameSettings")
-- Camera math utility library
local CameraUtils = require(script:WaitForChild("CameraUtils"))
-- Load camera modules (Note: each returns a new() constructor function but does not instantiate a controller)
local ClassicCamera = require(script:WaitForChild("ClassicCamera"))
local OrbitalCamera = require(script:WaitForChild("OrbitalCamera"))
local LegacyCamera = require(script:WaitForChild("LegacyCamera"))
-- Load character-occlusion modules
local Invisicam = require(script:WaitForChild("Invisicam"))
local Poppercam do
local success, useNewPoppercam = pcall(UserSettings().IsUserFeatureEnabled, UserSettings(), "UserNewPoppercam")
if success and useNewPoppercam then
Poppercam = require(script:WaitForChild("Poppercam"))
else
Poppercam = require(script:WaitForChild("Poppercam_Classic"))
end
end
-- Load the near-field character transparency controller
local TransparencyController = require(script:WaitForChild("TransparencyController"))
local MouseLockController = require(script:WaitForChild("MouseLockController"))
-- Table of camera controllers that have been instantiated as needed
local instantiatedCameraControllers = {}
local instantiatedOcclusionModules = {}
-- Management of which options appear on the Roblox User Settings screen
do
local PlayerScripts = Players.LocalPlayer:WaitForChild("PlayerScripts")
local canRegisterCameras = pcall(function() PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Default) end)
if canRegisterCameras then
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Follow)
PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Classic)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Default)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Follow)
PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Classic)
end
end
-- Current active camera and occlusion module
local activeCameraController = nil
local activeOcclusionModule = nil
local activeTransparencyController = nil
local activeMouseLockController = nil
local currentComputerCameraMovementMode = nil
-- Connections to events
local cameraSubjectChangedConn = nil
local cameraTypeChangedConn = nil
local function GetCameraMovementModeFromSettings()
local cameraMode = Players.LocalPlayer.CameraMode
-- Lock First Person trumps all other settings and forces ClassicCamera
if cameraMode == Enum.CameraMode.LockFirstPerson then
return CameraUtils.ConvertCameraModeEnumToStandard(Enum.ComputerCameraMovementMode.Classic)
end
local devMode, userMode
if UserInputService.TouchEnabled then
devMode = CameraUtils.ConvertCameraModeEnumToStandard(Players.LocalPlayer.DevTouchCameraMode)
userMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.TouchCameraMovementMode)
else
devMode = CameraUtils.ConvertCameraModeEnumToStandard(Players.LocalPlayer.DevComputerCameraMode)
userMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovementMode)
end
if devMode == Enum.DevComputerCameraMovementMode.UserChoice then
-- Developer is allowing user choice, so user setting is respected
return userMode
end
return devMode
end
local function ActivateOcclusionModule( occlusionMode )
local newModuleCreator = nil
if occlusionMode == Enum.DevCameraOcclusionMode.Zoom then
newModuleCreator = Poppercam
elseif occlusionMode == Enum.DevCameraOcclusionMode.Invisicam then
newModuleCreator = Invisicam
else
warn("CameraScript ActivateOcclusionModule called with unsupported mode")
return
end
-- First check to see if there is actually a change. If the module being requested is already
-- the currently-active solution then just make sure it's enabled and exit early
if activeOcclusionModule and activeOcclusionModule:GetOcclusionMode() == occlusionMode then
if not activeOcclusionModule:GetEnabled() then
activeOcclusionModule:Enable(true)
end
return
end
-- Save a reference to the current active module (may be nil) so that we can disable it if
-- we are successful in activating its replacement
local prevOcclusionModule = activeOcclusionModule
-- If there is no active module, see if the one we need has already been instantiated
activeOcclusionModule = instantiatedOcclusionModules[newModuleCreator]
-- If the module was not already instantiated and selected above, instantiate it
if not activeOcclusionModule then
activeOcclusionModule = newModuleCreator.new()
if activeOcclusionModule then
instantiatedOcclusionModules[newModuleCreator] = activeOcclusionModule
end
end
-- If we were successful in either selecting or instantiating the module,
-- enable it if it's not already the currently-active enabled module
if activeOcclusionModule then
local newModuleOcclusionMode = activeOcclusionModule:GetOcclusionMode()
-- Sanity check that the module we selected or instantiated actually supports the desired occlusionMode
if newModuleOcclusionMode ~= occlusionMode then
warn("CameraScript ActivateOcclusionModule mismatch: ",activeOcclusionModule:GetOcclusionMode(),"~=",occlusionMode)
end
-- Deactivate current module if there is one
if prevOcclusionModule then
-- Sanity check that current module is not being replaced by itself (that should have been handled above)
if prevOcclusionModule ~= activeOcclusionModule then
prevOcclusionModule:Enable(false)
else
warn("CameraScript ActivateOcclusionModule failure to detect already running correct module")
end
end
-- Occlusion modules need to be initialized with information about characters and cameraSubject
-- Invisicam needs the LocalPlayer's character
-- Poppercam needs all player characters and the camera subject
if occlusionMode == Enum.DevCameraOcclusionMode.Invisicam then
-- Optimization to only send Invisicam what we know it needs
if Players.LocalPlayer.Character then
activeOcclusionModule:CharacterAdded(Players.LocalPlayer, Players.LocalPlayer.Character)
end
else
-- Poppercam and any others that get added in the future and need the full player list for raycast ignore list
for _, player in pairs(Players:GetPlayers()) do
if player and player.Character then
activeOcclusionModule:CharacterAdded(player, player.Character)
end
end
activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.CameraSubject)
end
-- Activate new choice
activeOcclusionModule:Enable(true)
end
end
-- When supplied, legacyCameraType is used and cameraMovementMode is ignored (should be nil anyways)
-- Next, if userCameraCreator is passed in, that is used as the cameraCreator
local function ActivateCameraController( cameraMovementMode, legacyCameraType )
local newCameraCreator = nil
if legacyCameraType~=nil then
--[[
This function has been passed a CameraType enum value. Some of these map to the use of
the LegacyCamera module, the value "Custom" will be translated to a movementMode enum
value based on Dev and User settings, and "Scriptable" will disable the camera controller.
--]]
if legacyCameraType == Enum.CameraType.Scriptable then
if activeCameraController then
activeCameraController:Enable(false)
activeCameraController = nil
return
end
elseif legacyCameraType == Enum.CameraType.Custom then
cameraMovementMode = GetCameraMovementModeFromSettings()
elseif legacyCameraType == Enum.CameraType.Track then
-- Note: The TrackCamera module was basically an older, less fully-featured
-- version of ClassicCamera, no longer actively maintained, but it is re-implemented in
-- case a game was dependent on its lack of ClassicCamera's extra functionality.
cameraMovementMode = Enum.ComputerCameraMovementMode.Classic
elseif legacyCameraType == Enum.CameraType.Follow then
cameraMovementMode = Enum.ComputerCameraMovementMode.Follow
elseif legacyCameraType == Enum.CameraType.Orbital then
cameraMovementMode = Enum.ComputerCameraMovementMode.Orbital
elseif legacyCameraType == Enum.CameraType.Attach or
legacyCameraType == Enum.CameraType.Watch or
legacyCameraType == Enum.CameraType.Fixed then
newCameraCreator = LegacyCamera
else
warn("CameraScript encountered an unhandled Camera.CameraType value: ",legacyCameraType)
end
end
if not newCameraCreator then
if cameraMovementMode == Enum.ComputerCameraMovementMode.Classic or
cameraMovementMode == Enum.ComputerCameraMovementMode.Follow or
cameraMovementMode == Enum.ComputerCameraMovementMode.Default then
newCameraCreator = ClassicCamera
elseif cameraMovementMode == Enum.ComputerCameraMovementMode.Orbital then
newCameraCreator = OrbitalCamera
else
warn("ActivateCameraController did not select a module.")
return
end
end
-- Create the camera control module we need if it does not already exist in instantiatedCameraControllers
local newCameraController = nil
if not instantiatedCameraControllers[newCameraCreator] then
newCameraController = newCameraCreator.new()
instantiatedCameraControllers[newCameraCreator] = newCameraController
else
newCameraController = instantiatedCameraControllers[newCameraCreator]
end
-- If there is a controller active and it's not the one we need, disable it,
-- if it is the one we need, make sure it's enabled
if activeCameraController then
if activeCameraController ~= newCameraController then
activeCameraController:Enable(false)
activeCameraController = newCameraController
activeCameraController:Enable(true)
elseif not activeCameraController:GetEnabled() then
activeCameraController:Enable(true)
end
elseif newCameraController ~= nil then
activeCameraController = newCameraController
activeCameraController:Enable(true)
end
if activeCameraController then
if cameraMovementMode~=nil then
activeCameraController:SetCameraMovementMode(cameraMovementMode)
elseif legacyCameraType~=nil then
-- Note that this is only called when legacyCameraType is not a type that
-- was convertible to a ComputerCameraMovementMode value, i.e. really only applies to LegacyCamera
activeCameraController:SetCameraType(legacyCameraType)
end
end
end
-- Note: The active transparency controller could be made to listen for this event itself.
local function OnCameraSubjectChanged()
if activeTransparencyController then
activeTransparencyController:SetSubject(game.Workspace.CurrentCamera.CameraSubject)
end
if activeOcclusionModule then
activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.CameraSubject)
end
end
local function OnCameraTypeChanged(newCameraType)
if newCameraType == Enum.CameraType.Scriptable then
if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
-- Forward the change to ActivateCameraController to handle
ActivateCameraController(nil, newCameraType)
end
-- Note: Called whenever workspace.CurrentCamera changes, but also on initialization of this script
local function OnCurrentCameraChanged()
local currentCamera = game.Workspace.CurrentCamera
if not currentCamera then return end
if cameraSubjectChangedConn then
cameraSubjectChangedConn:Disconnect()
end
if cameraTypeChangedConn then
cameraTypeChangedConn:Disconnect()
end
cameraSubjectChangedConn = currentCamera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
OnCameraSubjectChanged(currentCamera.CameraSubject)
end)
cameraTypeChangedConn = currentCamera:GetPropertyChangedSignal("CameraType"):Connect(function()
OnCameraTypeChanged(currentCamera.CameraType)
end)
OnCameraSubjectChanged(currentCamera.CameraSubject)
OnCameraTypeChanged(currentCamera.CameraType)
end
local function OnLocalPlayerCameraPropertyChanged(propertyName)
if propertyName == "CameraMode" then
-- CameraMode is only used to turn on/off forcing the player into first person view. The
-- Note: The case "Classic" is used for all other views and does not correspond only to the ClassicCamera module
if Players.LocalPlayer.CameraMode == Enum.CameraMode.LockFirstPerson then
-- Locked in first person, use ClassicCamera which supports this
if not activeCameraController or activeCameraController:GetModuleName() ~= "ClassicCamera" then
ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(Enum.DevComputerCameraMovementMode.Classic))
end
if activeCameraController then
activeCameraController:UpdateForDistancePropertyChange()
end
elseif Players.LocalPlayer.CameraMode == Enum.CameraMode.Classic then
-- Not locked in first person view
local cameraMovementMode = GetCameraMovementModeFromSettings()
ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
else
warn("Unhandled value for property player.CameraMode: ",Players.LocalPlayer.CameraMode)
end
elseif propertyName == "DevComputerCameraMode" or
propertyName == "DevTouchCameraMode" then
local cameraMovementMode = GetCameraMovementModeFromSettings()
ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
elseif propertyName == "DevCameraOcclusionMode" then
ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
elseif propertyName == "CameraMinZoomDistance" or propertyName == "CameraMaxZoomDistance" then
if activeCameraController then
activeCameraController:UpdateForDistancePropertyChange()
end
elseif propertyName == "DevTouchMovementMode" then
elseif propertyName == "DevComputerMovementMode" then
elseif propertyName == "DevEnableMouseLock" then
-- This is the enabling/disabling of "Shift Lock" mode, not LockFirstPerson (which is a CameraMode)
-- Note: Enabling and disabling of MouseLock mode is normally only a publish-time choice made via
-- the corresponding EnableMouseLockOption checkbox of StarterPlayer, and this script does not have
-- support for changing the availability of MouseLock at runtime (this would require listening to
-- Player.DevEnableMouseLock changes)
end
end
local function OnUserGameSettingsPropertyChanged(propertyName)
if propertyName == "ComputerCameraMovementMode" then
local cameraMovementMode = GetCameraMovementModeFromSettings()
ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
-- These remaining UserGameSettings properties are not currently used by camera scripts in Lua
-- and these cases could be removed.
elseif propertyName == "ComputerMovementMode" then
elseif propertyName == "ControlMode" then
elseif propertyName == "GamepadCameraSensitivity" then
elseif propertyName == "MouseSensitivity" then
elseif propertyName == "RotationType" then
elseif propertyName == "TouchCameraMovementMode" then
elseif propertyName == "TouchMovementMode" then
end
end
game.Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(OnCurrentCameraChanged)
-- Connect listeners to camera-related properties
for _, propertyName in pairs(PLAYER_CAMERA_PROPERTIES) do
Players.LocalPlayer:GetPropertyChangedSignal(propertyName):Connect(function()
OnLocalPlayerCameraPropertyChanged(propertyName)
end)
end
for _, propertyName in pairs(USER_GAME_SETTINGS_PROPERTIES) do
UserGameSettings:GetPropertyChangedSignal(propertyName):Connect(function()
OnUserGameSettingsPropertyChanged(propertyName)
end)
end
--[[
Main RenderStep Update. The camera controller and occlusion module both have opportunities
to set and modify (respectively) the CFrame and Focus before it is set once on CurrentCamera.
The camera and occlusion modules should only return CFrames, not set the CFrame property of
CurrentCamera directly.
--]]
local function Update(dt)
local newCameraCFrame = nil
local newCameraFocus = nil
if activeCameraController then
newCameraCFrame, newCameraFocus = activeCameraController:Update(dt)
activeCameraController:ApplyVRTransform()
else
newCameraCFrame = game.Workspace.CurrentCamera.CFrame
newCameraFocus = game.Workspace.CurrentCamera.Focus
end
if activeOcclusionModule then
newCameraCFrame, newCameraFocus = activeOcclusionModule:Update(dt, newCameraCFrame, newCameraFocus)
end
-- Here is where the new CFrame and Focus are set for this render frame
game.Workspace.CurrentCamera.CFrame = newCameraCFrame
game.Workspace.CurrentCamera.Focus = newCameraFocus
-- Update to character local transparency as needed based on camera-to-subject distance
if activeTransparencyController then
activeTransparencyController:Update()
end
end
-- [[ old module migrating code below this point ]] --
local hasLastInput = false
local lastInputType = nil
-- Formerly getCurrentCameraMode, this function resolves developer and user camera control settings to
-- decide which camera control module should be instantiated. The old method of converting redundant enum types
local function GetCameraControlChoice()
local player = Players.LocalPlayer
if player then
if (hasLastInput and lastInputType == Enum.UserInputType.Touch) or UserInputService.TouchEnabled then
-- Touch
if player.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice then
return CameraUtils.ConvertCameraModeEnumToStandard( UserGameSettings.TouchCameraMovementMode )
else
return CameraUtils.ConvertCameraModeEnumToStandard( player.DevTouchCameraMode )
end
else
-- Computer
if player.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice then
local computerMovementMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovementMode)
return CameraUtils.ConvertCameraModeEnumToStandard(computerMovementMode)
else
return CameraUtils.ConvertCameraModeEnumToStandard(player.DevComputerCameraMode)
end
end
end
end
hasLastInput = pcall(function()
lastInputType = UserInputService:GetLastInputType()
UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
lastInputType = newLastInputType
end)
end)
local function OnCharacterAdded(player, char)
if activeOcclusionModule then
activeOcclusionModule:CharacterAdded(player, char)
end
end
local function OnCharacterRemoving(player, char)
if activeOcclusionModule then
activeOcclusionModule:CharacterRemoving(player, char)
end
end
local function OnPlayerAdded(player)
player.CharacterAdded:Connect(function(character) OnCharacterAdded(player, character) end)
player.CharacterRemoving:Connect(function(character) OnCharacterRemoving(player, character) end)
end
local function OnMouseLockToggled()
if activeMouseLockController then
local mouseLocked = activeMouseLockController:GetIsMouseLocked()
local mouseLockOffset = activeMouseLockController:GetMouseLockOffset()
if activeCameraController then
activeCameraController:SetIsMouseLocked(mouseLocked)
activeCameraController:SetMouseLockOffset(mouseLockOffset)
end
end
end
OnPlayerAdded(Players.LocalPlayer)
activeTransparencyController = TransparencyController.new()
activeTransparencyController:Enable(true)
if not UserInputService.TouchEnabled then
activeMouseLockController = MouseLockController.new()
local toggleEvent = activeMouseLockController:GetBindableToggleEvent()
if toggleEvent then
toggleEvent:Connect(OnMouseLockToggled)
end
end
ActivateCameraController(GetCameraControlChoice())
ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
OnCurrentCameraChanged() -- Does initializations and makes first camera controller
RunService:BindToRenderStep("cameraRenderUpdate", Enum.RenderPriority.Camera.Value, Update)
function CameraScript:GetActiveCameraController()
return activeCameraController
end
return CameraScript