Hello fellow developers! I’ve been recently working on a hover car with a smooth camera that lags behind a bit to give a more immersive and dynamic feel to it as some of you might have already know/seen from this video I posted on twitter and youtube which you can see here. But there’s one annoying issue with either the car’s physics or the camera that I’ve been having a lot of trouble figuring out. If you look closely in the video you’ll see that the car is stuttering (moving back and forth in small increments rapidly).
Example of the car stuttering: https://gyazo.com/6605698627927566fc7607460dfa4246
At first I thought it was most likely due to the method of smoothing that my camera uses. I had the camera using the lerp function and setting the alpha to a constant multiplied by the delta time step:
Camera.CFrame = Camera.CFrame:lerp(newCamCF, constant*dt)
So initially I looked around the devforums to see if someone had come across the same or a similar issue and I found a thread on the client bugs category (which you can see here) that showed a kart stuttering when a camera was being set in a similar way to mine. It also showed that the stutter seemingly went away when the camera was directly set to the player’s head each render step. The thread went on and people suggested many things including damped springs (which I’ll get to shortly) but nothing was working for me.
I tried the following (and more):
- lerp function
- Smooth damping
- Messing with BindToRenderStep render priority
- Connecting to RenderStepped, Heartbeat, and Stepped
- Compensated for variable time steps (as mentioned at start of post)
- Using TweenService instead of lerp
- Damped springs (over damped, under damped, critically damped)
The main “solution” most people said worked for them or was heavily suggested is springs, more specifically critically damped springs. So I spent a massive amount of time on top of the other chunk of time I had already spent messing with all the other attempts to fix it trying to figure out springs. I tried a number of different spring models and none of them worked. I came across an article (http://www.ryanjuckett.com/programming/damped-springs/) which had a spring model that included functionality for under damped, over damped, and most importantly critically damped springs. So I ported into roblox and ended up with this spring module (feel free to use it just be sure to abide by the copyright from the bottom of the article above):
local spring = {}
local epsilon = 0.0001
local exp = math.exp
local cos = math.cos
local sin = math.sin
local vec3 = Vector3.new
function spring.new(initial, target)
local self = setmetatable({}, {__index = spring})
self.initial = initial
self.pos = initial
self.velocity = vec3()
self.target = target
self.zeta = 1 -- Damping ratio
self.omega = 6 -- Angular frequency
return self
end
function spring:update(dt)
local x0 = self.pos - self.target
if self.zeta > 1 + epsilon then
-- Over damped
local za = -self.omega*self.zeta
local zb = self.omega*(self.zeta*self.zeta - 1)^0.5
local z0, z1 = za-zb, za+zb
local expt0, expt1 = exp(z0*dt), exp(z1*dt)
local c1 = (self.velocity - x0*z1)/(-2*zb)
local c2 = x0 - c1
self.pos = self.target + c1*expt0 + c2*expt1
self.velocity = c1*z1*expt0 + c2*z1*expt1
elseif self.zeta > 1 - epsilon then
-- Critically damped
local expt = exp(-self.omega*dt)
local c1 = self.velocity + self.omega * x0
local c2 = (c1*dt + x0)*expt
self.pos = self.target + c2
self.velocity = (c1*expt) - (c2*self.omega)
else
-- Under damped
local omegaZeta = self.omega*self.zeta
local alpha = self.omega*(1 - self.zeta*self.zeta)^0.5
local exp = exp(-dt*omegaZeta)
local cos = cos(dt*alpha)
local sin = sin(dt*alpha)
local c2 = (self.velocity + x0*omegaZeta)/alpha
self.pos = self.target + exp*(x0*cos + c2*sin)
self.velocity = -exp*((x0*omegaZeta - c2*alpha)*cos + (x0*alpha + c2*omegaZeta)*sin)
end
end
return spring
Zeta is the damping ratio and omega is the angular frequency. By setting the zeta to 1 it should be result in a critically damped spring which was acclaimed as the supposed fix for the issue. But it was not successful in fixing the issue and the stutter was still very much so there. At this point I had practically tried everything possible to fix it. I’ve spent literally days just trying to fix this one small issue, and as anyone who’s reading this could probably understand, it was extremely defeating. So I began to think that the car might actually be stuttering and the camera isn’t to blame at all. The first thing I did was set up a fixed (not moving) camera looking one direction at the car and then I drove the car and looked closely to see if it was stuttering and I found that it was indeed stuttering. Here’s a video you can watch to see it yourself: Click here to see the video. It’s really hard to notice and is more easily noticeable when the car is going backwards since it’s not going as fast just look really closely and you’ll see it stuttering/juddering back and forth a bit. So let’s move onward to how the physics are set up for the car. The car uses vector forces although it did use body thrusts previously (but it makes no difference which one is used as for both cases the stuttering occurs equally as much). There are 4 “thrusters” that are parts welded to the car’s base which have vector forces. Each physics update these 4 thrusters raycast down and check if they are within the suspension range, if so then the thruster pushes up based on the square of the distance it has from the ground. Then it factors in a friction constant by using the thruster part’s velocity and subtracts the resulting force pushing up from the ground by the resulting friction vector. This is how I’m currently accounting for friction:
Thrust.Force = Thrust.Force - (pointCF:inverse()*CF(pointCF.p+pointVel)).p*Config.Friction
These 4 thrusters do all the movement for the car, they accelerate the car forwards and backwards, turn the car, do the flips and rolls, etc…
Now let’s jump forward to present time. I recently came up with a method of reducing the stuttering by having the visual appearance of the car lag behind where the actual car base’s cframe. I did this by lerping to the offset between where the car details currently are (Motor6D’d to a “DetailHook” part) multiplied by the car base’s cframe. I also smooth damped the offset using Crazyman32’s smooth damp module.
local Offset = detailHook.CFrame:inverse()*(carBaseCF*CF(0, 0, -0.615))
local px, py, pz, xx, yx, zx, xy, yy, zy, xz, yz, zz = Offset:components()
local offsetVec = smoothOffset:Update(lastOffset, vec3(px, py, pz), 0.045)
local opx, opy, opz = offsetVec.x, offsetVec.y, offsetVec.z
Offset = CF(opx, opy, opz, xx, yx, zx, xy, yy, zy, xz, yz, zz)
detailHook.CFrame = detailHook.CFrame:lerp(carBaseCF*Offset, 11*dt)
lastOffset = offsetVec
This is the result: https://gyazo.com/646e6bcba852a4bc089ab39b990a5c2b
As you can see the stuttering is still there although it looks smoother and not as bad as before.
The reason I’m asking for help is because I’m not happy with this result. I want to completely eliminate the stuttering and that method also has it’s own issues which is latency between where the visuals of the car are and where the car actually is as you can see in this gif. I can tweak it so that it smooths it more harshly making the stutter less and less perceivable but then it makes the car less responsive to due the increasingly high latency.
So, in conclusion, I’d highly appreciate any help with this. I’m convinced that the physics/car itself is the cause of the stuttering and at this point if you tell me it’s the camera I’ll have a hard time believing you unless you can give me a good explanation as to why it is because I’ve tried so many things with the camera (especially critically damped springs) and from the above evidence it’s really pointing towards the car itself and not the camera. Maybe it’s a bug with roblox physics (and if so something needs to be done about this), maybe it’s a small error with how I’m accounting for friction with the thrusters, or maybe its something I haven’t even thought of. Personally I’m leaning more on the side of it being an issue with roblox physics since I believe you could so something similar on unity with a camera lagging behind an object and it would work just fine without stutter even if the alpha for the lerp was static (Although don’t take my word for it). I’ve been working tirelessly to figure this out and I’m just about ready to flip a table trying to fix this. If you’ve made it this far I’d like to thank you for sticking around and hopefully you can provide some ensight on the issue.