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
sorry for necro-ing, but for me what worked is actually setting it to RunService.Heartbeat while RunService.Stepped completely ruined my camera. The difference between these is that Heartbeat runs AFTER the physics simulation while Stepped runs before it. I’m not particularly sure why it fixed my camera in particular but it did!
So, for anybody finding this topic, try setting your camera loop to RunService.Heartbeat instead of RenderStepped or Stepped and see if that fixes it
Hi, i have encountered a similar issue not too long ago, except with a viewmodel. The fix for me was similar to what @SubtotalAnt8185 said in his post:
Changing the update frame can cause a difference with systems like these, and both RenderStepped and Stepped run in different times than we want (before rendering the frame), but what you want is changing it to PreRender, which will make all the calculations happen before rendering the following frame.
RenderStepped is actually the same as PreRender, but it has been deprecated, and was replaced by PreRender.
RenderStepped in Roblox Documentation
RenderStepped
Fires every frame, prior to the frame being rendered.
Migration Note
This event has been superseded by PreRender which should be used for new work.
Stepped runs every frame prior to the physics calculation, which is not the environment we want the calculations to run in.
Stepped in Roblox Documentation
Stepped
Fires every frame, prior to the physics simulation.
Migration Note
This event has been superseded by PreSimulation which should be used for new work.
This is not a guaranteed fix for your particular issue, but for me when the viewmodel jittered in a similar way it worked, so give it a try maybe it works.
Let me know if something isnt clear, my main language isnt english.
Yes, that makes perfect sense as the camera system I’m using appears to set the camera at a different time. The new names for these events are very intuitive now.
This doesn’t work for me, i’ve looked through pretty much every devforum post with a remotely similar issue however the viewmodel still jitters forward and back when im moving in a linear way. Sorry for necro but if anybody else had this issue and nothing on the forum fixed it i would like to know what did.
Hello! I have posted a dev forum about jittering cameras before, and I believe that I have the solution to fix the issue.
Use RunService.Stepped or RunService.Heartbeat instead of RunService.RenderStepped. Based on my perspective, each RunService function obviously has different runtimes.
If that didn’t solve the issue, try using an exponential interpolation equation. I utilized this for my custom OTS camera, and it surprisingly worked.
function Exponential(deltaTime: number, smoothness: number)
return 1 - 0.01^(deltaTime * (smoothness or defaultSmoothness))
end
Unlike applying a linear function/equation, which results in a straight line, exponential equations, by their nature, have a rate of change in speed until they reach a certain x-y coordinate. This makes the exponential equation reasonably smoother.
If these do not work, try utilizing a Spring module, though I find this solution smoother. For me, try fixing the smoothness value, and you may find your sweet spot.