Hey! I am working on a weapon system that uses an over-the-shoulder (OTS) camera system. Arbeiters originally authored the OTS system, and I have since taken it and made changes to it to better fit my needs.
My current issue is that characters seem to be a tad jittery/glitchy when trying to follow the camera. I have attempted to mess with the formula, and to no avail, it’s still a problem. The camera update does use RenderStepped and the camera is quite fluid. It is solely the character alignment that is having issues.
I could use some help here from anyone familiar with camera systems. Thanks!
What the issue looks like:
The issue is more prominent when in the zoomed/aim state:
I have no issue providing the entirety of the script because it was originally open-source, and should stay that way:
local OTS_Cam = {}
-- ROBLOX Services
local PlayersService = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
assert(not RunService:IsServer(), "Error: OTS_Cam should only be run on the Client!")
-- CONSTANTS
OTS_Cam.Player = PlayersService.LocalPlayer or PlayersService.PlayerAdded:Wait()
local SHOULDER_DIRECTION = {RIGHT = 1, LEFT = -1}
OTS_Cam.settings = {
Default = {
FOV = 70,
Offset = Vector3.new(2.5, 2.5, 8),
VerticalAngleLimits = NumberRange.new(-45, 45),
Sensitivity = 3,
LerpSpeed = 0.5
},
Zoomed = {
FOV = 40,
Offset = Vector3.new(2, 2, 3),
VerticalAngleLimits = NumberRange.new(-45, 45),
Sensitivity = 1.5,
LerpSpeed = 0.5
}
}
OTS_Cam.SavedCameraSettings = nil
OTS_Cam.SavedMouseBehavior = nil
OTS_Cam.CurrentSettings = nil
OTS_Cam.CurrentMode = nil
OTS_Cam.HorizontalAngle = 0
OTS_Cam.VerticalAngle = 0
OTS_Cam.ShoulderDirection = SHOULDER_DIRECTION.RIGHT
OTS_Cam.IsCharacterAligned = false
OTS_Cam.IsMouseLocked = false
OTS_Cam.IsEnabled = false
local function Lerp(x, y, a)
return x + (y - x) * a
end
function OTS_Cam.SetCameraMode(cameraMode: string)
assert(cameraMode ~= nil, "Error: Argument 1 nil or missing")
assert(typeof(cameraMode) == "string", "Error: string expected, got " .. typeof(cameraMode))
assert(OTS_Cam.settings[cameraMode] ~= nil, "Error: Attempt set an unrecognized CameraMode " .. cameraMode)
if OTS_Cam.IsEnabled == false then
warn("Warning: Attempt to change CameraMode without enabling OTS Cam")
return
end
OTS_Cam.CurrentMode = cameraMode
OTS_Cam.CurrentSettings = OTS_Cam.settings[cameraMode]
end
function OTS_Cam.SetAlignCharacter(alignCharacter: boolean)
assert(alignCharacter ~= nil, "Error: Argument 2 nil or missing")
assert(typeof(alignCharacter) == "boolean", "Error: boolean expected, got " .. typeof(alignCharacter))
OTS_Cam.IsCharacterAligned = alignCharacter
-- Avoid awkward fighting between character movement rotation and camera alignment rotation
local humanoid = OTS_Cam.Player.Character and OTS_Cam.Player.Character:FindFirstChild("Humanoid")
if alignCharacter then
if humanoid then
humanoid.AutoRotate = false
end
else
if humanoid then
humanoid.AutoRotate = true
end
end
end
function OTS_Cam.SetMouseStep(steppedIn: boolean)
assert(steppedIn ~= nil, "Error: Argument 1 nil or missing")
assert(typeof(steppedIn) == "boolean", "Error: boolean expected, got " .. typeof(steppedIn))
if OTS_Cam.IsEnabled == false then
warn("Warning: Attempt to change MouseStep without enabling OTS Cam")
return
end
OTS_Cam.IsMouseLocked = steppedIn
if steppedIn then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
function OTS_Cam.SetShoulderDirection()
if OTS_Cam.ShoulderDirection == -1 then
OTS_Cam.ShoulderDirection = 1
else
OTS_Cam.ShoulderDirection = -1
end
end
function OTS_Cam.SaveDefaultCameraSettings()
local currentCamera = workspace.CurrentCamera
OTS_Cam.SavedCameraSettings = {
FieldOfView = currentCamera.FieldOfView,
CameraSubject = currentCamera.CameraSubject,
CameraType = currentCamera.CameraType
}
OTS_Cam.SavedMouseBehavior = UserInputService.MouseBehavior
end
function OTS_Cam.LoadDefaultCameraSettings()
local currentCamera = workspace.CurrentCamera
for setting, value in OTS_Cam.SavedCameraSettings do
currentCamera[setting] = value
end
UserInputService.MouseBehavior = OTS_Cam.SavedMouseBehavior
end
function OTS_Cam.Update()
local currentCamera = workspace.CurrentCamera
local currentCameraSettings = OTS_Cam.CurrentSettings
currentCamera.CameraType = Enum.CameraType.Scriptable
if OTS_Cam.IsMouseLocked then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
local mouseDelta = UserInputService:GetMouseDelta() * currentCameraSettings.Sensitivity
OTS_Cam.HorizontalAngle -= mouseDelta.X/currentCamera.ViewportSize.X
OTS_Cam.VerticalAngle -= mouseDelta.Y/currentCamera.ViewportSize.Y
OTS_Cam.VerticalAngle = math.rad(math.clamp(math.deg(OTS_Cam.VerticalAngle), currentCameraSettings.VerticalAngleLimits.Min, currentCameraSettings.VerticalAngleLimits.Max))
local character = OTS_Cam.Player.Character
local humanoidRootPart = (character ~= nil) and (character:FindFirstChild("HumanoidRootPart"))
if humanoidRootPart then
currentCamera.FieldOfView = Lerp(
currentCamera.FieldOfView,
currentCameraSettings.FOV,
currentCameraSettings.LerpSpeed
)
local offset = currentCameraSettings.Offset
offset = Vector3.new(offset.X * OTS_Cam.ShoulderDirection, offset.Y, offset.Z)
local newCameraCFrame = CFrame.new(humanoidRootPart.Position) *
CFrame.Angles(0, OTS_Cam.HorizontalAngle, 0) *
CFrame.Angles(OTS_Cam.VerticalAngle, 0, 0) *
CFrame.new(offset)
newCameraCFrame = currentCamera.CFrame:Lerp(newCameraCFrame, currentCameraSettings.LerpSpeed)
-- Character alignment
if OTS_Cam.IsCharacterAligned then
local newHumanoidRootPartCFrame = CFrame.new(humanoidRootPart.Position) *
CFrame.Angles(0, OTS_Cam.HorizontalAngle, 0)
humanoidRootPart.CFrame = humanoidRootPart.CFrame:Lerp(newHumanoidRootPartCFrame, currentCameraSettings.LerpSpeed)
end
currentCamera.CFrame = newCameraCFrame
end
end
function OTS_Cam.ConfigureStateForEnabled()
-- Initialize settings
OTS_Cam.SaveDefaultCameraSettings()
OTS_Cam.SetCameraMode("Default")
if OTS_Cam.CurrentSettings.AlignCharacter then
OTS_Cam.IsCharacterAligned = true
end
OTS_Cam.SetMouseStep(true)
OTS_Cam.SetShoulderDirection(SHOULDER_DIRECTION.RIGHT)
-- Calculate angles
local cameraCFrame = workspace.CurrentCamera.CFrame
local x, y, z = cameraCFrame:ToOrientation()
OTS_Cam.HorizontalAngle = y
OTS_Cam.VerticalAngle = x
end
function OTS_Cam.ConfigureStateForDisabled()
OTS_Cam.LoadDefaultCameraSettings()
OTS_Cam.SetCameraMode("Default")
OTS_Cam.SetShoulderDirection(SHOULDER_DIRECTION.RIGHT)
OTS_Cam.SetMouseStep(false)
OTS_Cam.HorizontalAngle = 0
OTS_Cam.VerticalAngle = 0
end
function OTS_Cam.Enable()
assert(OTS_Cam.IsEnabled == false, "Error: Attempt to enable OTS Cam while already enabled")
OTS_Cam.IsEnabled = true
OTS_Cam.ConfigureStateForEnabled()
-- Bind update to renderstep
RunService:BindToRenderStep(
"OTS_Cam",
Enum.RenderPriority.Camera.Value - 10,
function()
if OTS_Cam.IsEnabled then
OTS_Cam.Update()
end
end
)
end
function OTS_Cam.Disable()
assert(OTS_Cam.IsEnabled == true, "Error: Attempt to disable OTS Cam while already disabled")
OTS_Cam.ConfigureStateForDisabled()
OTS_Cam.IsEnabled = false
-- Unbind update from renderstep
RunService:UnbindFromRenderStep("OTS_Cam")
end
UserInputService.InputBegan:Connect(function(inputObject, gameProcessedEvent)
if gameProcessedEvent == false and OTS_Cam.IsEnabled then
if inputObject.KeyCode == Enum.KeyCode.Z then
OTS_Cam.SetShoulderDirection()
end
if inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
OTS_Cam.SetCameraMode("Zoomed")
end
if inputObject.KeyCode == Enum.KeyCode.LeftControl then
if OTS_Cam.IsEnabled then
OTS_Cam.SetMouseStep(not OTS_Cam.IsMouseLocked)
end
end
end
end)
UserInputService.InputEnded:Connect(function(inputObject, gameProcessedEvent)
if gameProcessedEvent == false and OTS_Cam.IsEnabled then
if inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
OTS_Cam.SetCameraMode("Default")
end
end
end)
return OTS_Cam