Why is my custom camera movement so unsmooth?

I’m working on a go kart racing game sort of like Mario Kart, and I’ve been having many issues with my camera following algorithm. As you can see it is following the kart like I want, however you can see shaky movement and it generally being unsmooth. Why is this?

The script (a LocalScript in StarterPack):

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")

local Camera = workspace.Camera
local r = 15

local player = Players.LocalPlayer
local model = workspace.kartModels:WaitForChild(player.Name)

local hitboxPart = workspace.karts:WaitForChild(player.Name .. ":hitbox")
local kart = workspace.karts:WaitForChild(player.Name)

local p = Instance.new("Part")
p.Parent = workspace
p.Anchored = true
p.Transparency = 1

local function tween(object, duration, properties, easeStyle, easeDirection)
	local tweenService = game:GetService("TweenService")
	local tweenInfo = TweenInfo.new(duration, easeStyle, easeDirection)

	local t = tweenService:Create(object, tweenInfo, properties)

	t:Play()
	return t
end

if player == Players.LocalPlayer then
	Camera.CameraType = Enum.CameraType.Scriptable

	hitboxPart.Changed:Connect(function() --hitboxPart.Changed:Connect(function()
		local targetCFrame = model.PrimaryPart.CFrame
		local dir = kart.direction.Value
		local drift = kart.drift.Value

		local D = (dir + 180) % 360

		updateCamera(targetCFrame, D)
	end)
end

function updateCamera(targetCFrame, D)
	local pos = targetCFrame.Position + Vector3.new(
		r * math.cos(math.rad(D)),
		5,
		r * math.sin(math.rad(D))
	)
		
	local c = CFrame.new(pos, targetCFrame.Position + Vector3.new(0, 3.5, 0))
	
	tween(Camera, 0.05, {CFrame = CFrame.new(c.Position, targetCFrame.Position + Vector3.new(0, 3.5, 0))}, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
end

The camera will update whenever the kart’s hitbox part has been updated (i.e. it moves, turns, etc). This, at least in my opinion, is quite inefficient and performance-heavy. There’s most certainly a better approach that can be taken, but sadly I don’t know what that is.

What can I do to stop this kind of behavior and ensure everything is silky smooth? Any help is appreciated, thanks!

3 Likes

Have you tried a faster refresh rate? A tween at 0.05 would translate to 20 fps, which would obviously be somewhat choppy when animating anything.

1 Like

Camera control should always be called by the RenderStepped event to avoid choppiness, although this isn’t always enough, it’s an important start.

1 Like

I’ve actually already tried RenderStepped (hence the runService definition in the script I provided) and it really doesn’t change much. For clarity’s sake here’s a video I recorded of the camera script using RenderStepped:

The “0.05” in the script does not control the refresh rate. It controls the rate at which the camera will tween to the target position (higher numbers will mean the camera trails far behind the kart, while smaller numbers provide a more accurate position but make everything choppy)

I know that if you use Tweens, they will stop the current animation in order to play the existing one.
An animation plays, but before it reaches its goal. Another one plays, and it has to reach another goal from where it is at, until it gets interrupted again… I personally do not think Tweens would be the best way to do it, especially if the timing is consistent.

What I would do is use lerps instead of tweens so that the animations keep up with the RenderStepped rate instantly instead of animations playing and then doing catch-up.

Edit: Worst case scenario, you use an invisible part that is offsetting from the player and the camera follows that part around. Of course, the part would use a BodyForce, AlignPosition, or something to keep up.

2 Likes