Camera using springs is super jittery

I was working on a camera module using springs to create a sort of dragging behind the player effect, the camera works pretty well but there’s one big problem.

Example of what I mean:

The character is super jittery in the clip for some reason and I’m not sure what’s causing it.

Here’s the spring module I’m using:

The main portion of the camera script


	local cameraSubject = CameraSubject:Clone()
	cameraSubject.Parent = self.hrp

	local positionSpring = Spring.new(Vector3.new())
	positionSpring.d = 0.8
	positionSpring.s = 15
	
	local bodyGyro = Instance.new("BodyGyro")
	bodyGyro.MaxTorque = Vector3.new(0,4e5,0)
	bodyGyro.P = 4e5
	bodyGyro.Parent = self.hrp
	
	local cameraAngleX = 0
	local cameraAngleY = 0
	
	local cameraSubjectOffset = CFrame.new(0,4,0)
	local actualOffset = CFrame.new(0,0,12)
	
	UserInputService.InputChanged:Connect(function(inputObject)
		if (inputObject.UserInputType == Enum.UserInputType.MouseMovement) then			
			cameraAngleX = cameraAngleX - inputObject.Delta.X
			-- Reduce vertical mouse/touch sensitivity and clamp vertical axis
			cameraAngleY = math.clamp(cameraAngleY-inputObject.Delta.Y*0.4, -75, 75)
			-- Rotate root part CFrame by X delta
			bodyGyro.CFrame = bodyGyro.CFrame * CFrame.Angles(0, math.rad(-inputObject.Delta.X), 0)
		end
	end)
	
	RunService:BindToRenderStep("Camera",Enum.RenderPriority.Camera.Value + 3,function(dt)
		local camera = workspace.CurrentCamera
		camera.CameraType = Enum.CameraType.Scriptable
		UserInputService.MouseBehavior = self.shiftLock and Enum.MouseBehavior.LockCenter or UserInputService.MouseBehavior
		self.humanoid.AutoRotate = not self.shiftLock
		bodyGyro.Parent = self.shiftLock and self.hrp or nil

		cameraSubject.CFrame = self.hrp.CFrame:ToWorldSpace(cameraSubjectOffset)
		
		local cameraCFrameGoal = cameraSubject.CFrame
		
		local cameraPositionGoal = cameraCFrameGoal.Position
		
		positionSpring.t = cameraPositionGoal
		
		local cameraPosition = positionSpring.p
		
		local finalCameraCFrame = (CFrame.new(cameraPosition) * CFrame.Angles(0, math.rad(cameraAngleX), 0) * CFrame.Angles(math.rad(cameraAngleY), 0, 0)):ToWorldSpace(CFrame.new(actualOffset.X, actualOffset.Y, actualOffset.Z))
		camera.CFrame = finalCameraCFrame
	end)

Have you tried just doing something like

camera.CFrame = oldCFrame:Lerp(updatedCFrame, 0.5)

I thought the problem might’ve been that so I tried tweening and lerping it although the camera was still jittering, honestly I have no idea why it’s happening

might just have to settle for a boring regular camera :((

Also tried playing around with render priority but that change anything either

The problem is that your spring is smoother than the actual movement of the physical part (which can actually bit a bit jerky at times). So the stuttering is actually the part and not the camera.

The way to fix this is to always base the first part of your camera equation by setting it to the CFrame of the part you’re following. And then add any offsets to it.

local subjectCFrame = cameraSubject.CFrame
camera.CFrame = subjectCFrame

But that will just give you a static camera without the spring effect. To add the spring, it’s a bit more tricky now, since you can’t just base it off the raw input of the spring anymore. We need the offset position, not world position.

There’s a bunch of different ways to go about doing that. One way would be to do something like this:

local subjectCFrame = cameraSubject.CFrame
spring.Target = subjectCFrame.Position
local posOffset = spring.Position - subjectCFrame.Position

camera.CFrame = subjectCFrame + posOffset
1 Like

Pass the time function in as the second argument, which locks the spring to physics time, over real time.

local spring = Spring.new(initialPosition, time)

4 Likes

ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

So here’s the new version, it’s still going wonky, probably something wrong with my implementation because my brain has been thoroughly fried by the jittering for the last couple of hours

updated code


----- Services -----

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

----- Module Scripts -----

local Spring = require(script:WaitForChild("Spring"))
local Maid = require(ReplicatedStorage:WaitForChild("Maid"))

----- Private Variables -----

local CameraSubject = script:WaitForChild("CameraSubject")
local MouseIcon = ""

local Camera = {}
Camera.__index = Camera

----- Public Methods -----

function Camera.new(player)
	local self = setmetatable({},Camera)
	
	self.maid = Maid.new()
	
	self.player = player
	self.character = player.Character or player.CharacterAdded:Wait()
	self.humanoid = self.character:WaitForChild("Humanoid")
	self.hrp = self.character:WaitForChild("HumanoidRootPart")
	
	self.mouse = player:GetMouse()
	
	self.cameraLock = true
	
	self:initialise()
	
	return self
end

function Camera:initialise()
	local cameraSubject = CameraSubject:Clone()
	cameraSubject.Parent = self.hrp
	
	local spring = Spring.new(Vector3.new(),time)
	spring.d = 0.8
	spring.s = 15
	
	local bodyGyro = Instance.new("BodyGyro")
	bodyGyro.MaxTorque = Vector3.new(0,4e5,0)
	bodyGyro.P = 4e5
	bodyGyro.Parent = self.hrp
	
	local cameraAngleX = 0
	local cameraAngleY = 0
	
	local cameraSubjectOffset = CFrame.new(0,4,12)
	local actualOffset = CFrame.new(0,0,12)
	
	self.maid:GiveTask(UserInputService.InputChanged:Connect(function(inputObject)
		if (inputObject.UserInputType == Enum.UserInputType.MouseMovement) then				
			cameraAngleX = cameraAngleX - inputObject.Delta.X
			-- Reduce vertical mouse/touch sensitivity and clamp vertical axis
			cameraAngleY = math.clamp(cameraAngleY-inputObject.Delta.Y*0.4, -75, 75)
			-- Rotate root part CFrame by X delta
			bodyGyro.CFrame = bodyGyro.CFrame * CFrame.Angles(0, math.rad(-inputObject.Delta.X), 0)
		end
	end))
	
	self.maid:GiveTask(RunService.Heartbeat:Connect(function(dt)
		local camera = workspace.CurrentCamera
		camera.CameraType = Enum.CameraType.Scriptable
		UserInputService.MouseBehavior = self.cameraLock and Enum.MouseBehavior.LockCenter or UserInputService.MouseBehavior
		self.humanoid.AutoRotate = not self.cameraLock
		bodyGyro.Parent = self.cameraLock and self.hrp or nil
		
		cameraSubject.Position = self.hrp.CFrame:ToWorldSpace(cameraSubjectOffset).Position
		
		local subjectCFrame = cameraSubject.CFrame
		spring.t = subjectCFrame.Position
		
		local posOffset = spring.Position - subjectCFrame.Position
		
		camera.CFrame = subjectCFrame + posOffset 
		camera.CFrame = camera.CFrame * CFrame.Angles(0, math.rad(cameraAngleX), 0) * CFrame.Angles(math.rad(cameraAngleY), 0, 0)
	end))
end

function Camera:Destroy()	
	self.maid:DoCleaning()
end

return Camera

Not sure if you’ve fixed this but BindToRenderStep and the Spring module helped me a lot.

Setting the render priority like that makes it smoother.

local CameraSteppedConnection = RunService:BindToRenderStep("CameraStepped", Enum.RenderPriority.Camera.Value + 1, CameraSettings)	
1 Like