Jittery Car Suspension (Scripted)

I have recently replaced my cars physics based suspension with a code based one, which while being far better overall has a flaw I need help addressing.

The suspension system I use is prone to Jittering for unknown reasons:

Here is the video I sourced the code from:

Even In the original system the cars suspension is prone to Jittering, I am 99% sure this problem is not a frame rate issue as I modified the code heavily to work with all frame rates and tested it thoroughly,

Local Car:

Humanoid.Seated:Connect(function(seated,seatPart)
	if seated and seatPart then
		local vehicle = seatPart.Parent

		local carPrim = vehicle.PrimaryPart

		local id = vehicle:GetAttribute("CarID")
		local props = carProps[id]

		local carModel = seatPart.Parent
		local carPrim = carModel.PrimaryPart

		local SpringLengthMemory = {}
		for i,v in pairs(props.WheelPositions)do SpringLengthMemory[i] = 0.5 end

		--current steer pos
		local SmoothSteer = 0


		local serverTakeOverWait = 0
		while true do

			if seatPart.Parent:FindFirstChild("VehicleIdentifierID") and seatPart:IsA("VehicleSeat")then--whatever that gives out its car

				local delta = game["Run Service"].Heartbeat:Wait()

				for i,v in pairs(props.WheelPositions)do

					local carCFrame = carPrim.CFrame
					local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(Vector3.new(v.x,v.y - props.wheelRadius,v.z))).p
					local rayDirection = -carCFrame.UpVector * props.SuspensionMaxLength
					local rayParams = RaycastParams.new()
					rayParams.FilterDescendantsInstances = {vehicle}
					local raycast = workspace:Raycast(rayOrigin,rayDirection,rayParams)

					if raycast then

						local LocalVelocity = CFrame.lookAt(Vector3.zero,carCFrame.LookVector,carCFrame.UpVector):ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))

						if not WheelRotations[vehicle] then
							WheelRotations[vehicle] = {}
							WheelRotations[vehicle].STEER = 0
							for i,v in pairs(props.WheelPositions)do WheelRotations[vehicle][i] = 0.5 end
						end
						WheelRotations[vehicle][i] += LocalVelocity.z * delta

						local DatWeld = vehicle.Wheels[i].Weld

						local weldInitPos = CFrame.new(v - Vector3.new(0,(rayOrigin - raycast.Position).magnitude,0))
						if string.sub(i,1,1)=='F' then--if front
							--lerp smoothing below, but for some reason SteerFloat flickers
							WheelRotations[vehicle].STEER = WheelRotations[vehicle].STEER + (vehicle[props.driverSeatName].SteerFloat - WheelRotations[vehicle].STEER)*delta*6
							DatWeld.C0 = weldInitPos * CFrame.Angles(0,-math.rad(WheelRotations[vehicle].STEER * props.SteerAngle),0)
						else--DatWeld.C0.Rotation
							DatWeld.C0 = weldInitPos
						end
						if v.x>0 then 
							DatWeld.C0 = DatWeld.C0 * CFrame.Angles(0,math.rad(180),0)
							DatWeld.C0 = DatWeld.C0 * CFrame.Angles(-WheelRotations[vehicle][i],0,0)
						else
							DatWeld.C0 = DatWeld.C0 * CFrame.Angles(WheelRotations[vehicle][i],0,0)
						end



					end

				end

				local carPrim = carModel.PrimaryPart

				SmoothSteer = math.abs(
					seatPart.SteerFloat-SmoothSteer)<=delta*5 and seatPart.SteerFloat or SmoothSteer+math.sign(
					seatPart.SteerFloat-SmoothSteer)*delta*5

				for wheelName, originalPosition in pairs(props.WheelPositions)do

					local carCFrame = carPrim.CFrame
					local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(originalPosition)).p
					local rayDirection = -carCFrame.UpVector * (props.SuspensionMaxLength + props.wheelRadius)
					local rayParams = RaycastParams.new()
					rayParams.FilterDescendantsInstances = {carModel}
					local raycast = workspace:Raycast(rayOrigin,rayDirection,rayParams)

					if raycast then

						local RaycastDistance = (rayOrigin - raycast.Position).magnitude

						local SpringLength = math.clamp(RaycastDistance - props.wheelRadius, 0, props.SuspensionMaxLength)
						local StiffnessForce = props.Stiffness * (props.SuspensionMaxLength - SpringLength)
						local DamperForce = props.Damper * (( SpringLengthMemory[wheelName] - SpringLength) / delta)
						local SuspensionForceVec3 = carCFrame.UpVector * (StiffnessForce + DamperForce) * 100 * delta

						local RotationsOnlyWheelDirCFrame = CFrame.lookAt(Vector3.zero,carCFrame.LookVector,carCFrame.UpVector)
						if string.sub(wheelName,1,1)=='F' then
							RotationsOnlyWheelDirCFrame = RotationsOnlyWheelDirCFrame * CFrame.Angles(0,-math.rad(SmoothSteer * props.SteerAngle),0)
						end
						local LocalVelocity = RotationsOnlyWheelDirCFrame:ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))

						local Xforce = RotationsOnlyWheelDirCFrame.RightVector * -LocalVelocity.x * props.wheelFriction * delta * 25
						local Zforce = RotationsOnlyWheelDirCFrame.LookVector * seatPart.ThrottleFloat * props.torque * (math.sign(-LocalVelocity.z)==seatPart.Throttle and(1 - math.min(1,math.abs(LocalVelocity.z)/props.MaxSpeed))or 1) * 100 * delta

						SpringLengthMemory[wheelName] = SpringLength


						carPrim:ApplyImpulseAtPosition(SuspensionForceVec3 + Xforce + Zforce, raycast.Position)

					else
						SpringLengthMemory[wheelName] = props.SuspensionMaxLength
					end
					
					if VisualiseRaycast then 
						PlrGui.gaag.ag.CurrentCamera = workspace.CurrentCamera 
						PlrGui.gaag.ag2.CurrentCamera = workspace.CurrentCamera 
						coroutine.wrap(function()
							local p = Instance.new("Part",PlrGui.gaag.ag)
							p.Name = 'rayVis'p.Color = Color3.new(0,0,1)
							p.Anchored = true p.CanCollide = false 
							p.CollisionGroupId = 1 
							p.Material = Enum.Material.Neon 
							p.Shape = Enum.PartType.Cylinder 
							local pe = p:Clone()
							pe.Color = Color3.new(1,0,0)
							pe.Parent = p.Parent
							local Rdestination = (rayOrigin + rayDirection)
							if raycast then 
								local rDist = (rayOrigin - raycast.Position).magnitude 
								local s = rayOrigin - carCFrame.UpVector * rDist/2 
								p.CFrame = CFrame.lookAt(s, s + carCFrame.RightVector,-carCFrame.LookVector) 
								p.Size = Vector3.new(rDist, 0.1, 0.1)
								rDist = (raycast.Position - Rdestination).magnitude 
								s =  raycast.Position - carCFrame.UpVector * rDist/2 
								pe.Size = Vector3.new(rDist, 0.1, 0.1)
								pe.CFrame = CFrame.lookAt(s, s + carCFrame.RightVector,-carCFrame.LookVector)
							else p.Size = Vector3.new((rayOrigin - Rdestination).magnitude, 0.1, 0.1)
								local s = rayOrigin + rayDirection/2 
								p.CFrame = CFrame.lookAt(s, s + carCFrame.RightVector,-carCFrame.LookVector)
								Rdestination = Rdestination + carCFrame.UpVector*0.02 
								pe.CFrame = CFrame.lookAt(Rdestination, Rdestination + carCFrame.RightVector,-carCFrame.LookVector)
								pe.Size = Vector3.new(0,0.1,0.1)
							end 
							local pp = p:Clone()
							pp.Parent = PlrGui.gaag.ag2 
							pp.Size = p.Size + Vector3.new(0.01,0.01,0.01)
							local ppe = pe:Clone() 
							ppe.Parent = pp.Parent 
							ppe.Size = pe.Size + Vector3.new(0.01,0.01,0.01)
							game["Run Service"].Heartbeat:Wait()
							pp:Destroy()
							ppe:Destroy()
							p:Destroy()
							pe:Destroy()
						end)()
					end 
					
				end




				if seatPart.Occupant ~= Humanoid then--check whatever that means that character still sits in car
					serverTakeOverWait += delta
					if serverTakeOverWait >= 0.5 then
						break
					end
				end

			end


		end
	end
end)

Car Properties:

local module = {

	Wingman = {
		driverSeatName = "FLSeat",
		
		SuspensionMaxLength = 2.8,
		wheelRadius = 1.4875,

		Stiffness = 45,
		Damper = 4,

		wheelFriction = 6.5,
		torque = 35,
		MaxSpeed = 90,
		SteerAngle = 30,

		WheelPositions = {
			RL = Vector3.new(3.424, 0, 5.8),
			RR = Vector3.new(-3.438, 0, 5.8),
			FL = Vector3.new(3.423, 0, -6.12),
			FR = Vector3.new(-3.438, 0, -6.12),
		},
	},


}
return module

I would greatly appreciate if anyone could think of the reason why the car suspension does this, Thank you for your time.

2 Likes

I do believe the issue is something with relying on Heartbeat.

Heartbeat in the documentation runs after the physics simulation,
which in tandem with all the for loops you have will cause a tiny delay.

A solution (although not the most efficient) would be using RenderStepped instead,
to run the code during the physics simulation.

Please correct me if I am wrong, but I do think this should help.

Wow, I can’t believe I didn’t think of this earlier, that was a really good idea

However sadly I don’t think it worked


still dealing with the jittery suspension

as a side note: tabbing out of the game makes the jittery suspension so much worse not quite sure why it does that.