I have been refactoring code from a Kart Racing game I made with the intent of creating an Authoritative Networking solution, So far everything has been working fine, except there is one problem I have been having,
I’ve noticed this very apparent jitter that appears on the physics handler for the kart whenever I try to manipulate the camera, and it does not appear to be the camera itself, but it appears to be the part itself snapping back and forth
Here’s a video example of what I’m talking about
If you watch the ground and the static parts around the baseplate you can see that those are moving smoothly, so I can deduce that this has to do with moving, unanchored parts running in the physics engine
--Just to be safe, here's my code for camera manipulation
runService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value - 1, function(dt)
--This value is an attribute I added to the code to be able to test smoothing rates
local smoothing = script:GetAttribute("Smoothing")
if target == nil then
--This simply finds whatever physical object is controlled by the player
getTarget()
else
if target:FindFirstChild("Direction") == nil then
target = nil
return
end
--Direction is a part inside of the target rig, it should always be in the center of the ball
local direction = target.Direction
--Physics is a part inside of the Target rig, it should be a (5,5,5) ball
local physics = target.Physics
--Get the goal CFrame, which should be behind the target and slightly above
local position = direction.CFrame.Position - (direction.CFrame.LookVector * 10)
local goalCF = CFrame.new(position + Vector3.new(0, 3, 0), direction.CFrame.Position)
--Lerp using a smoothing value
camera.CFrame = camera.CFrame:Lerp(goalCF, dt * smoothing)
end
end)
Is this just an artifact of Roblox’s physics engine? Or is there something wrong with my code?
If the code that updates the position of the kart isn’t synced to RenderStepped then you’ll get this phenomenon. It makes sense if you think carefully about it. The gist of the explanation is that even though both the kart and the cam are moving “smoothly” at their respective update frequencies, because those frequencies are different it appears that the kart isn’t at the center of the screen a lot of the time. It “drifts” a bit every time the camera updates but the kart doesn’t, and when the kart finally does update it snaps back into the correct relative position. If you remove the camera smoothing then it should actually get better, because if the camera is just at some fixed offset from the kart then it will look like the camera updates at the slower physics frame rate but still in lock step with the kart, so it never drifts relative to it and so there’s no “snapping back”.
You fix it by interpolating between physics frames in your render loop. Sorry, can’t give code examples as I’m actually trying to figure this out for myself right now for my own game
The jittering you are experiencing could be caused by the fact that the position and orientation of the kart is being controlled both by the physics engine and by the camera’s CFrame, which could lead to conflicts and inconsistencies. This is a common problem in games that use physics-based movement.
One way to solve this issue is to use the “steering” approach, where the camera doesn’t directly control the orientation of the kart, but instead gives hints to the physics engine about where the kart should go. This can be done by applying forces or impulses to the kart’s rigidbody, using the camera’s direction as a guide.
Here’s an example of how you could modify your camera script to use this approach:
runService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value - 1, function(dt)
local smoothing = script:GetAttribute("Smoothing")
if target == nil then
getTarget()
else
if target:FindFirstChild("Direction") == nil then
target = nil
return
end
local direction = target.Direction
local physics = target.Physics
local targetPosition = direction.CFrame.Position - (direction.CFrame.LookVector * 10)
local targetVelocity = (targetPosition - physics.Position) / dt
local force = (targetVelocity - physics.Velocity) * physics:GetMass()
physics:ApplyForce(force)
local goalCF = CFrame.new(targetPosition + Vector3.new(0, 3, 0), direction.CFrame.Position)
camera.CFrame = camera.CFrame:Lerp(goalCF, dt * smoothing)
end
end)
In this modified script, we calculate the desired velocity of the kart based on the camera’s target position and apply a force to the kart’s rigidbody to match that velocity. This should result in smoother and more consistent movement, as the physics engine is responsible for maintaining the position and orientation of the kart, while the camera just provides guidance.
Note that this solution may not work; it is just a suggestion for you to fix the code. I did not test those codes or methods before posting this. Feel free to reply to this thread and ask for more help if needed.
I’ve worked on my FPS game for the past few days so I finally know what is causing this!
change this to just smoothing then increase smoothing. Although it will be incompatible with variable framerates, it should work. What I did in my FPS game is I used SpringService to get the smoothness.
The difference is that the framerate is variable, meaning that it will go the same speed if you use a fixed alpha.
Exactly. Here’s the code for the module:
local physics = {}
do
physics.spring = {}
do
local spring = {}
physics.spring = spring
local e = 2.718281828459045
function spring.new(init)
local null = 0 * (init or 0)
local d = 1
local s = 1
local p0 = init or null
local v0 = null
local p1 = init or null
local t0 = os.clock()
local h = 0
local c1 = null
local c2 = null
local self = {}
local meta = {}
local function UpdateConstants()
if s == 0 then
h = 0
c1 = null
c2 = null
elseif d < 0.99999999 then
h = (1 - d * d) ^ 0.5
c1 = p0 - p1
c2 = d / h * c1 + v0 / (h * s)
elseif d < 1.00000001 then
h = 0
c1 = p0 - p1
c2 = c1 + v0 / s
else
h = (d * d - 1) ^ 0.5
local a = -v0 / (2 * s * h)
local b = -(p1 - p0) / 2
c1 = (1 - d / h) * b + a
c2 = (1 + d / h) * b - a
end
end
local function Pos(x)
if x < 0.001 then
return p0
end
if s == 0 then
return p0
elseif d < 0.99999999 then
local co = math.cos(h * s * x)
local si = math.sin(h * s * x)
local ex = e ^ (d * s * x)
return co / ex * c1 + si / ex * c2 + p1
elseif d < 1.00000001 then
local ex = e ^ (s * x)
return (c1 + s * x * c2) / ex + p1
else
local co = e ^ ((-d - h) * s * x)
local si = e ^ ((-d + h) * s * x)
return c1 * co + c2 * si + p1
end
end
local function Vel(x)
if x < 0.001 then
return v0
end
if s == 0 then
return p0
elseif d < 0.99999999 then
local co = math.cos(h * s * x)
local si = math.sin(h * s * x)
local ex = e ^ (d * s * x)
return s * (co * h - d * si) / ex * c2 - s * (co * d + h * si) / ex * c1
elseif d < 1.00000001 then
local ex = e ^ (s * x)
return -s / ex * (c1 + (s * x - 1) * c2)
else
local co = e ^ ((-d - h) * s * x)
local si = e ^ ((-d + h) * s * x)
return si * (h - d) * s * c2 - co * (d + h) * s * c1
end
end
local function PosVel(x)
if s == 0 then
return p0
elseif d < 0.99999999 then
local co = math.cos(h * s * x)
local si = math.sin(h * s * x)
local ex = e ^ (d * s * x)
return co / ex * c1 + si / ex * c2 + p1, s * (co * h - d * si) / ex * c2 - s * (co * d + h * si) / ex * c1
elseif d < 1.00000001 then
local ex = e ^ (s * x)
return (c1 + s * x * c2) / ex + p1, -s / ex * (c1 + (s * x - 1) * c2)
else
local co = e ^ ((-d - h) * s * x)
local si = e ^ ((-d + h) * s * x)
return c1 * co + c2 * si + p1, si * (h - d) * s * c2 - co * (d + h) * s * c1
end
end
UpdateConstants()
function self.GetPosVel()
return PosVel(os.clock() - t0)
end
function self.SetPosVel(p, v)
local time = os.clock()
p0, v0 = p, v
t0 = time
UpdateConstants()
end
function self:Accelerate(a)
local time = os.clock()
local p, v = PosVel(time - t0)
p0, v0 = p, v + a
t0 = time
UpdateConstants()
end
function meta:__index(index)
local time = os.clock()
if index == "p" then
return Pos(time - t0)
elseif index == "v" then
return Vel(time - t0)
elseif index == "t" then
return p1
elseif index == "d" then
return d
elseif index == "s" then
return s
end
end
function meta:__newindex(index, value)
local time = os.clock()
if index == "p" then
p0, v0 = value, Vel(time - t0)
elseif index == "v" then
p0, v0 = Pos(time - t0), value
elseif index == "t" then
p0, v0 = PosVel(time - t0)
p1 = value
elseif index == "d" then
if value == nil then
warn("nil value for d")
warn(debug.stacktrace())
value = d
end
p0, v0 = PosVel(time - t0)
d = value
elseif index == "s" then
if value == nil then
warn("nil value for s")
warn(debug.stacktrace())
value = s
end
p0, v0 = PosVel(time - t0)
s = value
elseif index == "a" then
local p, v = PosVel(time - t0)
p0, v0 = p, v + value
end
t0 = time
UpdateConstants()
end
return setmetatable(self, meta)
end
end
end
return physics