Jittery camera behaviour when dampened

I’ve been having a lot of trouble with smoothing my camera for a bit now. I’ve managed to get the overall smoothing right but I have two outstanding problems right now:

  • It tends to jitter when rotated on the X axis
  • When the camera’s rotation is negative, it returns to 0 instead of smoothly transitioning

Here are is an example:

and the code responsible for my camera:

local player = game.Players.LocalPlayer
local RunService = game:GetService("RunService")
local camera = game.Workspace.Camera
local mouse = player:GetMouse()
local userInputService = game:GetService("UserInputService")
local ts = game:GetService("TweenService")
local ti = TweenInfo.new(0.3)

local smoothDamp = require(game.ReplicatedStorage.SmoothDampAlgorithm)
local smoothX = smoothDamp.new()
local smoothY = smoothDamp.new()

local mouseX
local mouseY

local xRotation = 0
local yRotation = 0

local dampenedMouseX = 0
local dampenedMouseY = 0

local Sensitivity = 20

local character

local canMoveCam = true

local MouseDeltaMagnitude

player.CharacterAppearanceLoaded:Connect(function(char)
	canMoveCam = true
	character = char
	character.Humanoid.AutoRotate = false
	userInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	for i, v in pairs(char:GetChildren()) do
		if v:IsA("BasePart") and v.Name ~= "Head" then
			v.LocalTransparencyModifier = v.Transparency
			v:GetPropertyChangedSignal("LocalTransparencyModifier"):Connect(function()
				v.LocalTransparencyModifier = v.Transparency
			end)
		end
	end
	
	for _, v in pairs(char:GetChildren()) do
		if v:IsA("Accessory") then
			v:WaitForChild("Handle").Transparency = 1
		end
	end
	
	local cameraUpdate = RunService.RenderStepped:Connect(function(deltaTime)
		local mousePos = userInputService:GetMouseDelta()
		
		mouseX = mousePos.X * deltaTime * Sensitivity
		mouseY = mousePos.Y * deltaTime * Sensitivity
		
		MouseDeltaMagnitude = userInputService:GetMouseDelta().X
		
		xRotation -= mouseY
		xRotation = math.clamp(xRotation, -20, 60)
		
		local xRotDead = math.clamp(xRotation, -45, 45)
		
		yRotation = yRotation + mouseX
		
		mouseX = math.clamp(mouseX, mouseX -.2, mouseX + .2)
		
		dampenedMouseX = math.deg(smoothX:SmoothDampAngleF(camera.CFrame.Rotation.X, math.rad(xRotation), .4, deltaTime))
		dampenedMouseY = smoothY:SmoothDampAngleF(character.HumanoidRootPart.CFrame.Rotation.Y, mouseX, .4, deltaTime )

		camera.CFrame = CFrame.new(character.Head.CFrame.Position)
		if canMoveCam then
			-- Line of interest
			camera.CFrame = (camera.CFrame * ((character.HumanoidRootPart.CFrame.Rotation ) * CFrame.Angles(dampenedMouseX,0, 0)))
			character.HumanoidRootPart.CFrame = (character.HumanoidRootPart.CFrame * CFrame.Angles(0, -dampenedMouseY, 0))
		else
			camera.CFrame = (camera.CFrame * ( character.Head.CFrame.Rotation * CFrame.Angles( math.rad(xRotDead), math.rad(yRotation), 0)))
		end
	end)
	
	character.Humanoid.Died:Connect(function()
		canMoveCam = false
		task.wait(15)
		cameraUpdate:Disconnect()
	end)
end)

mouse.Button1Down:Connect(function()
	userInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	userInputService.MouseIconEnabled = false
end)

mouse.Button2Down:Connect(function()
	userInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	userInputService.MouseIconEnabled = false	
end)



and the smoothdamp module (Updated version of @sleitnick’s module which can be found here):

local SmoothDamp = {}
SmoothDamp.__index = SmoothDamp

function SmoothDamp.new()
	return setmetatable({
		MaxSpeed = math.huge;
		_update = time();
		_velocity = 1;
	}, SmoothDamp)
end

local function DeltaAngle(current, target)
	local delta = (target-current) % math.rad(360)
	if delta > math.rad(180) then
		delta -= math.rad(360)
	end
	
	return delta
end

function SmoothDamp:SmoothDampF(current, target, smoothTime, deltaTime)
	local currentVelocity = self._velocity
	local now = time()
	-- local deltaTime = (now - self._update)
	smoothTime = math.max(0.0001, smoothTime)

	local num = (2 / smoothTime)
	local num2 = (num * deltaTime)
	local d = (1 / (1 + num2 + 0.48 * num2 * num2 + 0.235 * num2 * num2 * num2))

	local change = (current - target)
	local originalTo = target

	local maxLength = (self.MaxSpeed * smoothTime)
	change = math.clamp(change, -maxLength, maxLength)
	target = (current - change)

	local temp = ((currentVelocity + num * change) * deltaTime)
	currentVelocity = ((currentVelocity - num * temp) * d)

	local output = (target + (change + temp) * d)
	if ((originalTo - current) > 0) == (output > originalTo) then
		output = originalTo
		currentVelocity = (output - originalTo) / deltaTime
	end

	self._velocity = currentVelocity
	self._update = now
	return output
end

function SmoothDamp:SmoothDampAngleF(current, target, smoothTime, deltaTime)
	target = current + DeltaAngle(current, target)
	return self:SmoothDampF(current, target, smoothTime, deltaTime)
end

return SmoothDamp

I’ve been stuck on this for so long now and any help would be massively appreciated!

I haven’t read your code, but it may be because you and Roblox are colliding. Consider updating the camera position after the camera is moved in the camera modules.

1 Like

Not sure if this helps but the camera is set to scriptable so all the code you see there handles the camera. Nothing else.

1 Like

Ahh, my mistake. :slight_smile: (lalalalalalalala30)

1 Like

I would use a spring module instead of the smoothed damp one.

I’m not sure about the math behind the dampener one, it should have some variables to control the responsiveness and speed but doesn’t which makes me thing it’s for a specific use case.

I would use Quenty’s spring module:

The spring equation is used basically universally for this, so I don’t know what the dampening module you have is for.

Edit:

This tutorial has some relevant information:

1 Like

The smooth damp module is taken from unity. Here are some sources:

I tried programming smoothdamp because it’s similar to springs, has a nice effect and I just wanted to do it myself with as little external modules as possible.

1 Like

Oh I see, smooth damp has a spring then uses some math to convert a smooth time into the spring formula.

I assume some of your math probably has an error then, I can’t seem to find it though.

These lines look error prone. The two variables are named the same but in different units and are doing different things.

dampenedMouseX = math.deg(smoothX:SmoothDampAngleF(camera.CFrame.Rotation.X, math.rad(xRotation), .4, deltaTime))
dampenedMouseY = smoothY:SmoothDampAngleF(character.HumanoidRootPart.CFrame.Rotation.Y, mouseX, .4, deltaTime )

I’m not sure why mouseX is used for smoothY also.

Have you tried flipping the order of these two?

camera.CFrame = (camera.CFrame * ((character.HumanoidRootPart.CFrame.Rotation ) * CFrame.Angles(dampenedMouseX,0, 0)))
			character.HumanoidRootPart.CFrame = (character.HumanoidRootPart.CFrame * CFrame.Angles(0, -dampenedMouseY, 0))

The first line uses the HRP CFrame and the next line sets it.

I’m not sure why your code is so complicated, usually all it needs to do is generate a basic rotation for the camera to face then take the current rotation and dampen it moving towards that.

1 Like

I do think its a math problem because no amount of tweaking and tinkering with the camera itself seems to work.

To clarify:

dampenedMouseX = math.deg(smoothX:SmoothDampAngleF(camera.CFrame.Rotation.X, math.rad(xRotation), .4, deltaTime))
dampenedMouseY = smoothY:SmoothDampAngleF(character.HumanoidRootPart.CFrame.Rotation.Y, mouseX, .4, deltaTime )

Control the right axis and there doesn’t seem to be any problem with them. They are just named weirdly becuase of an odd convention I used to follow a while ago (MouseX and MouseY are the mouses coordinates on the mouse’s axis. The naming is not based on the rotation of the camera or body).

I’ve flipped these two around:

camera.CFrame = (camera.CFrame * ((character.HumanoidRootPart.CFrame.Rotation ) * CFrame.Angles(dampenedMouseX,0, 0)))
character.HumanoidRootPart.CFrame = (character.HumanoidRootPart.CFrame * CFrame.Angles(0, -dampenedMouseY, 0))

Also, the reason the code is so messy is because I’m doing a lot experimentation. When I’m done, Ill definitely go over and refactor it :slight_smile:

Anyways, my main suspicion lies in two areas:

SmoothDamp:DeltaAngles() doesn’t work

or the more likely one

There is some sort of math error in the SmoothDampF() method.

1 Like

You could try graphing the function’s output to see if they’re working properly. This module makes that a lot faster and easier:

I read over your DeltaAngles implementation and I noticed a small inconsistency. I’m pretty sure Math.Repeat works like the module operator for positive numbers, but works a little differently with negative numbers.

The original line in C# is:

Mathf.Repeat((target - current), 360.0F)

Your implementation is:

(target-current) % math.rad(360)

The full implementation in C# is:

((inputAngle/360f)-(int)(inputAngle/360f))*360f

So I think it should be:

local twoPi = math.rad(360)
local inputAngle = target - current

((inputAngle / twoPi) - math.floor(inputAngle / twoPi)) * twoPi

(Source: line 354 of the C# code you send and this UF post)

Edit:
Actually I don’t think this is the problem. I think Lua’s modulo operator is smarter than the C# one.

1 Like