Shoulder Camera Issues

I’ve been trying to create an over the shoulder camera for a weapon system, and have managed to get it mostly working. I’ve been taking inspiration from a lot of different pieces of code found on the DevHub and other threads here on the forums. I have one issue though that I can’t seem to figure out.

When I change directions with my mouse the rotation of my player as well as the joint angles gets messed up as if it’s using the previous camera cframe.

Example Of Issue
Another Example

If I release the camera and use it again it usually fixes itself. I’m unsure where this is going wrong in my code and was wondering if anyone else could see my problem, thanks!

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local LocalPlayer = Players.LocalPlayer

local TweenService = require(script.Parent.TweenService)
local Camera = workspace.CurrentCamera
local Mouse = LocalPlayer:GetMouse()

local function setCharacterJoints(Character)
	local Joints = 
	{
		Neck = Character.Head:WaitForChild("Neck"),
		Waist = Character.UpperTorso:WaitForChild("Waist")
	}
	
	local CameraDirection = Character.PrimaryPart.CFrame:ToObjectSpace(Camera.CFrame).LookVector
	local CamDirectionY = math.clamp(CameraDirection.Y, -0.8, 0.8)
	local CamDirectionX = math.clamp(CameraDirection.X, -0.8, 0.8)
	
	Joints.Neck.C0 = CFrame.new(Joints.Neck.C0.Position) * CFrame.Angles(math.asin(CamDirectionY), -math.asin(CamDirectionX), 0)
	Joints.Waist.C0 = CFrame.new(Joints.Waist.C0.Position) * CFrame.Angles(math.asin(CamDirectionY), -math.asin(CamDirectionX), 0)
end

local function resetCharacterJoints(Character)
	local Joints = 
	{
		Neck = Character.Head:WaitForChild("Neck"),
		Waist = Character.UpperTorso:WaitForChild("Waist")
	}
	
	Joints.Neck.C0 = CFrame.new(Joints.Neck.C0.Position)
	Joints.Waist.C0 = CFrame.new(Joints.Waist.C0.Position)
end

local function updateCharacterRotation(Character, deltaX)
	local goal = {CFrame = Character.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(-deltaX), 0)}
	local properties = {Time = 0.01}
	TweenService.tween(Character.PrimaryPart, goal, properties)
end

local ShoulderCamera = {}
ShoulderCamera.Character = LocalPlayer.Character

function ShoulderCamera:UpdateCamera()
	local Character = ShoulderCamera.Character
	
	if Character and Character:FindFirstChild("HumanoidRootPart") then
		local startCFrame = CFrame.new(Character.PrimaryPart.CFrame.Position) * CFrame.Angles(0, math.rad(ShoulderCamera.RotationX), 0) * CFrame.Angles(math.rad(ShoulderCamera.RotationY), 0, 0)
		
		local cameraOffset = Vector3.new(2,4,ShoulderCamera.Zoom)
		
		if ShoulderCamera.Aiming then
		end
		
		local cameraCFrame = startCFrame:ToWorldSpace(CFrame.new(cameraOffset.X, cameraOffset.Y, cameraOffset.Z))
		local cameraFocus = startCFrame:ToWorldSpace(CFrame.new(cameraOffset.X, cameraOffset.Y, -10000))
		
		local goal = {FieldOfView = ShoulderCamera.FOV}
		local properties = {Time = 0.1, Style = "Sine", Dir = "Out"}
		TweenService.tween(Camera, goal, properties)
		
		local goal = {CFrame = CFrame.new(cameraCFrame.Position, cameraFocus.Position)}
		local properties = {Time = 0.01}
		TweenService.tween(Camera, goal, properties)
	end
end

function ShoulderCamera:Start()
	Camera.CameraType = "Scriptable"
	ShoulderCamera.Zoom = 10
	ShoulderCamera.FOV = 70
	ShoulderCamera.Connections = {}
	ShoulderCamera.RotationX = 0
	ShoulderCamera.RotationY = 0
	ShoulderCamera.CameraSpeed = 0.75
	ShoulderCamera.Aiming = false

	ShoulderCamera.Character.PrimaryPart.CFrame = CFrame.new(ShoulderCamera.Character.PrimaryPart.Position, Mouse.Hit.Position * Vector3.new(1,0,1) + Vector3.new(0,ShoulderCamera.Character.PrimaryPart.Position.Y,0))
	
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	
	ShoulderCamera.runConnection = RunService.RenderStepped:Connect(function()
		local Character = ShoulderCamera.Character
		
		if Character and Character:FindFirstChild("HumanoidRootPart") then
			ShoulderCamera:UpdateCamera()
			setCharacterJoints(Character)
			Character.Humanoid.AutoRotate = false
		end
	end)
	
	local function playerInput(actionName, inputState, inputObject)
		local Character = ShoulderCamera.Character
		
		if Character and Character:FindFirstChild("HumanoidRootPart") then
			if inputState == Enum.UserInputState.Change then
				ShoulderCamera.RotationX = ShoulderCamera.RotationX - inputObject.Delta.X * ShoulderCamera.CameraSpeed
				ShoulderCamera.RotationY = math.clamp(ShoulderCamera.RotationY-inputObject.Delta.Y*0.4* ShoulderCamera.CameraSpeed, -75, 75)
				
				updateCharacterRotation(Character, inputObject.Delta.X * ShoulderCamera.CameraSpeed)
			end
		end
	end
	ContextActionService:BindAction("PlayerInput", playerInput, false, Enum.UserInputType.MouseMovement, Enum.UserInputType.Touch)
	
	ShoulderCamera.Connections.Move = Mouse.Move:Connect(function()
		local Character = ShoulderCamera.Character
		if Character and Character:FindFirstChild("HumanoidRootPart") then
	
		end
	end)
	
	ShoulderCamera.Connections.InputBegan = UserInputService.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			ShoulderCamera.FOV = 60
			ShoulderCamera.CameraSpeed = 0.15
			ShoulderCamera.Aiming = true
		end
	end)
	
	ShoulderCamera.Connections.InputEnded = UserInputService.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			ShoulderCamera.FOV = 70
			ShoulderCamera.CameraSpeed = 0.75
			ShoulderCamera.Aiming = false
		end
	end)
	
	ShoulderCamera.Connections.WheelForward = Mouse.WheelForward:Connect(function()
		ShoulderCamera.Zoom = math.clamp(ShoulderCamera.Zoom - 1, 0, 10)
	end)
	
	ShoulderCamera.Connections.WheelForward = Mouse.WheelBackward:Connect(function()
		ShoulderCamera.Zoom = math.clamp(ShoulderCamera.Zoom + 1, 0, 10)
	end)
end

function ShoulderCamera:Stop()
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	ContextActionService:UnbindAction("PlayerInput")
	
	if ShoulderCamera.runConnection then
		ShoulderCamera.runConnection:Disconnect()
	end
	
	for _,connection in pairs(ShoulderCamera.Connections) do
		if connection then
			connection:Disconnect()
		end
	end
	
	local Character = ShoulderCamera.Character
	
	if Character and Character:FindFirstChild("HumanoidRootPart") then
		resetCharacterJoints(Character)
		
		Character.Humanoid.AutoRotate = true
		Camera.CameraType = "Custom"
		Camera.CameraSubject = Character.Humanoid
	end
end

return ShoulderCamera
1 Like