Raycast-based vehicle suspension - Issues when fps is low

I’m working on a vehicle suspension system that works kind of like a hovercraft, with a BodyThrust at each wheel. Every frame, each thruster gets its altitude with a downward raycast and adjusts the upward thrust with the help of a PID loop. You can see a similar thing in the default roblox racing game template. This works very nicely as long as the client stays above about 20 fps:

However there’s some issues if players drop below about 20fps, and for some reason a lot of people think that’s an acceptable framerate to play at. The thrusters can’t update quickly enough to keep things smooth, and end up oscillating which makes the truck undrivable:

I’ve tried adjusting the PID values and tweening the thrust values to try and smooth things out but I can’t get around the limitations of having the altitude values not updating quickly enough. As far as I know, there’s no way to get a loop that updates quicker than the clients framerate. Any ideas?

4 Likes

strange, I have also seen this behavior in jailbreak, where if you play at a fps lower than 30 (or i think 20) your car can go flying

2 Likes

the proportional term is responsible for instantaneous feedback; have you tried lowering your proportional bias value?

Yes it still bounces, but closer to the ground

It’d be pretty hacky but you could try clamping the allowed delta between the process variable and the return value whenever the delta time is above a certain threshold

local res = ProportionalTerm + IntergralTerm + DerivativeTerm

-- clamp if framerate is 30fps or lower
if dt >= .033 then
    return processVariable + math.clamp(res - processVariable, -LOW_FPS_MAX_DELTA, LOW_FPS_MAX_DELTA)
else
    return res
end
1 Like

It’s not perfect but this does help, it kinda drives like it has a blown out suspension but it is driveable

Increasing the integral bias seems like it should help there. Maybe dynamically by that delta time threshold. It’ll still feel kinda doggy and worn out but it’s going to slowly but surely move the process variable to the set point in those spaced out integration moments.

Hey, do you have any better solutions/workarounds for this?

We’ve pretty much got the exact same issue for scripted aircraft suspension.

I put together a slightly different system that still uses raycasts at each wheel, but instead of a bodythrust at each wheel, I do some math to figure out what height and orientation the vehicle should have as a whole. Then I can have all the constraints/forces in the primary part without worrying too much about them overcorrecting. It seems to work pretty well so far, there’s just a bit more things you have to fake, like the body roll.

Been tryna fix this exact issue for a year now :sweat:

The variables that are starting to now work are:

  1. Use Heartbeat.
  2. Use AlignPosition on each thruster [see pic for info on config].
  3. Use sine curve for dampening.
  4. With reference to the #3. Take the average of the past n thetas [including the current], and feed that into the sin method.

Eg:

-- CFG
local MAX_UPFORCE = 1000
local MAX_HEIGHT = 5
local MAX_THETAS = 10


-- Keep track of prev thetas
local Prev_Thetas = {}

do -- Do this every Heartbeat
   
    -- Calculate thruster's current height by raycasting
    current_height = ....

    -- Calculate theta for current step
    -- [Theta is what we give as the arg of math.sin]
    local theta = 1 - current_height / MAX_HEIGHT
    
    -- Append theta to Prev_Thetas table
    table.insert(Prev_Thetas, 1, theta)

   -- Take average of all thetas
   -- This will be our new theta 
   theta = 0
   for _, x in pairs(Prev_Thetas) do
      theta += x
   end 
   theta /= #Prev_Thetas
   
   -- Only want to track past 10 ticks
   table.remove(Prev_Thetas, 10)   

   -- Now we have the average of all thetas from past 10 ticks [including this one]
   -- Feed that into sin and multiply to get upforce
   local upforce = math.sin(theta) * MAX_UPFORCE
   
   -- Set the MaxAxesForce.Y of the thruster's AlignPosition to upforce 
   -- Also need to set the Position of the thruster's AlignPosition to the target
   --  height whilst respecting it's current orientation and position

end

1 Like

Lookup how to implement a physics substep, it fixed the low fps problem on my character controllers too

3 Likes