Issues with scripted camera stuttering

Hi all, I’m attempting to make a dynamic, fluid feeling camera to compliment the car chassis me and my team are using for our game. I am attempting to make it fairly reusable and independent so that I can just plug in the object instance of any chassis and, as long as I can find its drive seat, it should work. I want to have a smooth, slightly springy camera that follows the diverse movement of the cars. I have attempted to use numerous spring modules to achieve this, and many work quite well getting the desired effect I want.

However, after many iterations of my camera system, I continuously suffer from the camera “stuttering” when the subject is moving. What I mean by this, is the camera will follow the subject in the fluid, dynamic fashion I want, but the subject it is attempting to follow will visibly stutter and jitter around, sort of like screen tearing in a way, and not only is it distracting, but it is quite honestly highly unappealing.

I have redesigned the camera from the ground up, in the simplest and most straightforward way possible, to give a good, raw example of what I mean, without all the fluff and potential performance impacting details of the end result I want. For background info on the Spring Module, it simply solves springs for you, and it is fairly similar to any other spring solver out there: allowing you to plug in a position, target, stiffness and damping intensity. You can see that here:

local Camera = workspace.Camera
local RunService = game:GetService("RunService")

local Player = game.Players.LocalPlayer
local Backpack = Player:WaitForChild("Backpack")
local Char = Player:FindFirstChild("Character")

repeat
	Char = Player.Character
	wait()
until Char

local Root = Char.HumanoidRootPart

local RootOffset = CFrame.new(Vector3.new(0,8,12))

local SpringModule = require(script.Parent.Spring)
local Spring = SpringModule.new(Camera.CFrame.Position, Root.CFrame:ToWorldSpace(RootOffset).Position)
Spring.omega = 35

if Camera.CameraType ~= 6 then
	Camera.CameraType = 6
end


while true do
	local total,dt = RunService.Stepped:Wait()
	
	local CarValue = Backpack:FindFirstChild("PlayerValues"):FindFirstChild("CurrentCar").Value
	
	if CarValue then
		if CarValue.DriveSeat.Occupant then
			if CarValue.DriveSeat.Occupant == Char.Humanoid then
				
				if Camera.CameraType ~= 6 then
					Camera.CameraType = 6
				end
				
				Spring.target = Root.CFrame:ToWorldSpace(RootOffset).Position
				Spring:update(dt)
				
				Camera.CFrame = CFrame.new(Spring.pos) * CFrame.Angles(0,math.rad(Root.Orientation.Y),math.rad(Root.Orientation.Z))
				print(CarValue)
			else
				Camera.CameraType = 4
			end
		else
			Camera.CameraType = 4
		end
	else
		Camera.CameraType = 4
	end
end

In this attached video, you can see stutter from the camera that I’ve attached to an A-Chassis vehicle. As a disclaimer, this isn’t a result of A-Chassis, as this happens to many other objects, from basic parts with velocity, to also the player.
(Apologies for the 720p quality, I was originally going to make this a GIF, but I recorded for too long)

In terms of solutions, I have looked for many articles here on the DevForum about camera creation, despite their not being many that fixed/looked into my issue, as well as looking at previous posts, tech demos and resources regarding other people having this issue, but none of them seem to explain what/how they fixed this issue, people just explain an “alternative”.

However, I’m not looking for an alternative way to avoid this, I want to understand why this issue is happening in the first place, and obviously what I can do to fix this.

Any further questions, feel free to ask. All input is appreciated :slight_smile:

Thanks, Hirora

5 Likes

To me it looks as if its only happening exactly when the car shifts gears. Do you know if it has any association with changing velocities, or is it just high speeds?

Does this stutter with the default Roblox camera? Just want to eliminate factors. If I glossed over you mentioning it then forgive me.

I noticed you’re doing RunService.Stepped:Wait(). Usually camera movement is tied to RenderStepped via BindToRenderStep with RenderPriority.Camera. Is there any difference in doing that? I do recommend RenderStepped for camera scripts whether this works or not.

If it’s still the same are you doing any camera tweening? It almost seems like when you’re doing harsh movement, such as turning, is when the camera stutters. Maybe this is related to the camera position trying to update to accommodate the harsh movement change and tweening could help smooth it out.

The video might portray that, but no it, in gameplay, it doesn’t seem to be died to gear shifting and I think just high speeds in general- it mainly occurs during turning.

One theory I had was that, as a result of the 240Hz physics loop being faster than the 60Hz loop accessible but Roblox Lua, it might be updating faster than the camera can handle? Not sure about that though

This doesn’t happen at all with any of the default “follow”, “attached” etc. camera modes that roblox offers, but this is a camera I scripted with the camera, obviously, on Scriptable.

As for RenderStepped or BindToRenderStep with render priorities, I should’ve mentioned this, but I’ve already attempted both of those, pretty much any possible loop, to absolutely no avail- they all still jitter just as noticeably.

I’m not doing any form of camera tweening, I’m just simply repositioning the camera every step, feeding DT into the spring’s update function and pulling the result of that. I haven’t tried tweening, but I saw another post where someone had used tweening and they said it’s bad to continously initiate new tweens every frame?

I did some looking around on the forum and found an answer that seemed to resolve this. So it does seem to have to do with the hertz issue. Here’s the post I found Hover car physics stuttering?

The summed up answer is it seems to have a separate while loop constantly updating a variable called lastPhysicsDt via _, lastPhysicsDt = RunService.Stepped:wait()

Inside a BindToRenderStep with RenderPriority.Camera.Value + 3 is where it’s performing all the camera updates.

Taken from the code it seems to perform an if statement checking whether lastCFrame is not nil or is not the same as the current CFrame of the object

if ((lastCFrame == nil) or not (lastCFrame == part.CFrame)) then

If the condition above passes it will perform the camera CFrame update. The spring uses spring:update(lastPhysicsDt + lastFramesDt). It then proceeds to save the last CFrame of the object and set lastFramesDt to 0.

If the condition above doesn’t pass (lastCFrame is nil or it’s the same as the current CFrame) it goes to an else which sets lastFramesDt to the delta time given by RenderStepped

A simple pseudo script to help aid in how this is setup taken from the linked post above but simplified to showcase usage

local lastPhysicsDeltaTime = 0
local lastFramesDeltaTime = 0
local lastCFrame = nil

BindToRenderStep("CameraMovement", RenderPriority.Camera + 3, function(deltaTime)
    if ((lastCFrame == nil) or not (lastCFrame == targetCFrame)) then
        spring.target = cameraPosition
        spring:update(lastPhysicsDeltaTime + lastFramesDeltaTime)
        camera.CFrame = springPosition
        lastCFrame = targetCFrame
        lastFramesDt = 0
    else
        lastFramesDt = deltaTime
    end
end)

while true do
    local discard
    discard, lastPhysicsDeltaTime = RunService.Stepped:wait()
end
3 Likes

I have by no means implemented this yet, but I can definitely understand how this works, the pseudo script actually helps with understanding it well.
So by a very, very crude understanding, it just determines whether it actually reached or not and just continuously forces the spring accumulate in updates until it reaches the target successfully.

Seems great to me, I’ll implement this perhaps some time tomorrow and I’ll give updates!

Thanks a bunch! :smile:

2 Likes

Hey man, how did it end up working??
Also which spring module did you use?
Thanks