Template Raycast Vehicle System Physics Bug depending on user FPS

1. I’m trying to fix the default roblox racing template cars from flipping out when certain players have below 60 or above 60fps and provide them a stable fun driving experience.

  1. Random players are having their cars flip over instantly when driving forward or making turns and it seems to be users that have less than 60FPS or more than 60FPS with an unlocker program, meanwhile some players don’t flip out at all and enjoy driving normally. I’ve looked into possible problems like the runservice render stepped, stepped, heartbeat, I’ve tried changing the physicssteppingmethod from default, fixed, adaptive (which did nothing) I’m not sure what to do or what I’m overlooking.

Here’s the default roblox racing template place that I am using the exact cars from
and the exact car model and scripts I am using for my game
BuggyDefaultCarReference.rbxm (46.8 KB)

RayCastModule script:

local module = {}

function module.new(startPosition, startDirection)
	local maxDistance = startDirection.magnitude
	local direction = startDirection.unit
	local lastPosition = startPosition
	local distance = 0
	local ignore = {}
	
	local hit, position, normal
	
	repeat
		local ray = Ray.new(lastPosition, direction * (maxDistance - distance))
		hit, position, normal = game.Workspace:FindPartOnRayWithIgnoreList(ray, ignore, false, true)
		if hit then
			if not hit.CanCollide then
				table.insert(ignore, hit)
			end
		end
		distance = (startPosition - position).magnitude
		lastPosition = position
	until distance >= maxDistance - 0.1 or (hit and hit.CanCollide)
	return hit, position, normal
end

return module

Snippet from the LocalCarScript related to Runservice

--spawn(function()
	while game:GetService("RunService").Heartbeat:wait() and car:FindFirstChild("DriveSeat") and character.Humanoid.SeatPart == car.DriveSeat do
		--game:GetService("RunService").RenderStepped:wait()
		if IsGrounded() then
			if movement.Y ~= 0 then
				local velocity = humanoidRootPart.CFrame.lookVector * movement.Y * stats.Speed.Value
				humanoidRootPart.Velocity = humanoidRootPart.Velocity:Lerp(velocity, 0.1)
				bodyVelocity.maxForce = Vector3.new(0, 0, 0)
			else
				bodyVelocity.maxForce = Vector3.new(mass / 2, mass / 4, mass / 2)
			end
			local rotVelocity = humanoidRootPart.CFrame:vectorToWorldSpace(Vector3.new(movement.Y * stats.Speed.Value / 50, 0, -humanoidRootPart.RotVelocity.Y * 5 * movement.Y))
			local speed = -humanoidRootPart.CFrame:vectorToObjectSpace(humanoidRootPart.Velocity).unit.Z
			rotation = rotation + math.rad((-stats.Speed.Value / 5) * movement.Y)
			if math.abs(speed) > 0.1 then
				rotVelocity = rotVelocity + humanoidRootPart.CFrame:vectorToWorldSpace((Vector3.new(0, -movement.X * speed * stats.TurnSpeed.Value, 0)))
				bodyAngularVelocity.maxTorque = Vector3.new(0, 0, 0)
			else
				bodyAngularVelocity.maxTorque = Vector3.new(mass / 4, mass / 2, mass / 4)
			end
			humanoidRootPart.RotVelocity = humanoidRootPart.RotVelocity:Lerp(rotVelocity, 0.1)
			
			--bodyVelocity.maxForce = Vector3.new(mass / 3, mass / 6, mass / 3)
			--bodyAngularVelocity.maxTorque = Vector3.new(mass / 6, mass / 3, mass / 6)
		else
			bodyVelocity.maxForce = Vector3.new(0, 0, 0)
			bodyAngularVelocity.maxTorque = Vector3.new(0, 0, 0)
		end
		
		for i, part in pairs(car:GetChildren()) do
			if part.Name == "Thruster" then
				UpdateThruster(part)
			end
		end
	end
	for i, v in pairs(car:GetChildren()) do
		if v:FindFirstChild("BodyThrust") then
			v.BodyThrust:Destroy()
		end
	end
	bodyVelocity:Destroy()
	bodyAngularVelocity:Destroy()
	--camera.CameraType = oldCameraType
	script:Destroy()
--end)

CarScript snippet related to runservice

--spawn(function()
	while true do
		game:GetService("RunService").Stepped:wait()
		for i, part in pairs(car:GetChildren()) do
			if part.Name == "Thruster" then
				UpdateThruster(part)
			end
		end
		if car.DriveSeat.Occupant then
			local ratio = car.DriveSeat.Velocity.magnitude / stats.Speed.Value
			car.EngineBlock.Running.Pitch = 1 + ratio / 4
			bodyPosition.MaxForce = Vector3.new()
			bodyGyro.MaxTorque = Vector3.new()
		else
			local hit, position, normal = Raycast.new(car.Chassis.Position, car.Chassis.CFrame:vectorToWorldSpace(Vector3.new(0, -1, 0)) * stats.Height.Value)
			if hit and hit.CanCollide then
				bodyPosition.MaxForce = Vector3.new(mass / 5, math.huge, mass / 5)
				bodyPosition.Position = (CFrame.new(position, position + normal) * CFrame.new(0, 0, -stats.Height.Value + 0.5)).p
				bodyGyro.MaxTorque = Vector3.new(math.huge, 0, math.huge)
				bodyGyro.CFrame = CFrame.new(position, position + normal) * CFrame.Angles(-math.pi/2, 0, 0)
			else
				bodyPosition.MaxForce = Vector3.new()
				bodyGyro.MaxTorque = Vector3.new()
			end
		end
	end
--end)
  1. People seem to have similar problems with their own custom raycast rigs on the developerforum but I’m not sure if this model is having the same problems as them and can be traced back to the raycast being to blame or the runservice physics rendering.

I am not familiar with raycasting part of the car and I assume most of the flipping problem lay with the RunService because the flipping seems to directly correlate with players having less or more than normal FPS (60FPS) feel free to download the car file or open up the template place yourself and experiment with driving yourself or using an fpsunlock type program to test different framerates.

Appreciate if anyone can provide solutions so I can make amendments to this car system, it’s a really good, clean, moddable system and I would rather fix this system than make my own, thank you!

3 Likes

There should be a framerate compensation system. Here’s the idea: you take the Δt returned by the RunService events and use it as the divisor in an equation that calculates how much faster or slower the physics stuff should run. The dividend would be an arbitrary number, such as 60 since that’s the most common FPS.

multiplier = 60 / FPS
--if FPS is say 120, the multiplier would be 0.5 to slow physics down
--on the other hand, if the FPS is 30, the multiplier would be 2 to speed physics up
--To get the FPS from frametime, we take the multiplicative inverse of it
FPS = 1 / dt --**DO NOT USE ^-1, IT IS SUPER SLOW IN LUA
multiplier = 60 / (1 / dt) = 60 * dt
--sample code
RunService.Stepped:Connect(function(_, dt: number) --.Stepped is an exception; it returns TWO numbers, the latter one being delta-time
local multiplier: number = 60 * dt
--and then you multiply this multiplier to each physics-related task, exempli gratia:
bodyVelocity.maxForce = someRandomForce * multiplier

Another mention: the code is pretty horrific in terms of optimization, which can impact performance and hence be a factor in causing unstable framerates. Consider reading up on optimization tips, such as:

  • Use compound assignments (i.e. a += 1 instead of a = a + 1)
  • Use Vector3 constants (i.e. Vector3.zero instead of Vector3.new(0, 0, 0))
  • Hierarchy directory aliasing/localizing (i.e. storing workspace.Folder.Folder.Part as a variable and use that instead of indexing for it each time)
  • Not using deprecated functions (i.e. FindPartOnRayWithIgnoreList is deprecated)

In addition to bad optimization, there’s also a whole lot of bad practices, such as directly setting the .Velocity property of parts, which is NO BUENO and you should switch to things like :ApplyImpulse or smth

Or just go make your own vehicle module lol

4 Likes

Thank you I never considered doing such things that you mentioned this is incredibly helpful in remedying this flipping problem.

I’m open to creating a vehicle module but I am not skilled in scripting, however I am able to read and modify scripts after gaining understanding of what each part does; when you mentioned creating my own “vehicle module” did you mean re-writing only the raycastmodule segment of this car system?

Most people tend to work better on/around things that they’ve created themselves since they know the most, so I do encourage you to write the entire module from scratch if it means making future endeavors easier for you. But you decide; if you’re good at interpreting other people’s work, go ahead and keep it!