Help with raycast suspension

I’ve got a code that kind of works but doesn’t work as well. I need to make it so that it would slowly set the wheel above the ground.

local RunService = game:GetService("RunService")

local model = script.Parent
local Parts = model:WaitForChild("Parts")
local WheelParts = Parts:WaitForChild("WheelParts")

local VehicleSeat = Parts:WaitForChild("VehicleSeat")
local Chassis = model:WaitForChild("Chassis")

local MoveVelocity = model:WaitForChild("MoveVelocity")
local TurnVelocity = model:WaitForChild("TurnVelocity")

local Occupant = model:WaitForChild("Occupant")

local speed = 50
local maxSteer = 100

local gravity = workspace.Gravity
local modelMass = 0

local driveConnection

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {model}

for i,v in ipairs(model:GetDescendants()) do
	if v:IsA("BasePart") then
		modelMass += v.AssemblyMass
	end
end

local function Suspension(raycastResult, wheel)
	local SuspensionForce = wheel:FindFirstChild("SuspensionForce")
	
	if SuspensionForce then
		if raycastResult then
			local normal = raycastResult.Normal
			local force = normal * (modelMass * gravity)

			SuspensionForce.Force = force
		else
			SuspensionForce.Force = Vector3.new()
		end
	end
end

local function Drive()
	driveConnection = RunService.Heartbeat:Connect(function()
		for _, wheel in WheelParts:GetChildren() do
			if wheel:IsA("BasePart") then
				local wheelRadius = wheel.Size
				
				local rayOrigin = wheel.Position
				local rayDirection = wheel.CFrame.LookVector * wheelRadius
				local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
				
				if raycastResult then
					--MoveVelocity.Enabled = true
					TurnVelocity.AngularVelocity = Vector3.new(0, -math.rad(VehicleSeat.Steer * maxSteer), 0)
				else
					TurnVelocity.AngularVelocity = Vector3.new(0, 0, 0)
					MoveVelocity.Enabled = false
				end
				
				Suspension(raycastResult, wheel)
			end
		end
	end)
end

Drive()

Current Result:

3 Likes

Nice to see an attempt at raycast suspensions! If I understand your code correctly, you are shooting a ray downwards relative to the car, and if it hits the ground, applying a constant force to the vehicle. A step in making your suspension work better would be to make that force proportionate to how much it is compressed. For example, if it is compressed all the way, do a large force and if it is compressed just a little bit, apply less force. If there is not a raycast collision, meaning there is no compression, then just don’t apply a force at all.

Once you apply that, you should notice that the car bounces much smoother. However, you will notice it just bounces back and forth without slowing down. The solution for this is slightly more complicated, but with some tweaking, you can surely get it down. You will need to add something called a damper, which essentially means that the faster the spring compresses, the harder the spring is to compress. This is as if the spring has some friction, preventing it from bouncing forever.

Overall, your equation for spring force should look something like this:

stiffnessForce = (1 - ratio ) * stiffness
damperForce = ((lastRatio - ratio) / delta) * delta

local totalForce = stiffnessForce + damperForce

In the above code example, there are some variables you will need to have set. The ratio variable is like a percentage for how much a given suspension is compressed. Fully compressed = 0, fully extended = 1. You get this number by dividing the raycast length by how long the suspension should be.

Next, the lastRatio variable is the ratio of that exact suspension the last time the equation was run. There is also a delta variable which is required in order for the dampening to world correctly. The delta variable is how much time (in seconds) has passed since the previous frame. You can use runService:Stepped:(_, delta) to get the delta, or there are other methods you can employ.

Then, the stiffness variable is a setting for how much force should be exerted when the suspension is fully compressed. It looks like you already have a variable determining how much force is applied to the car when the suspension hits the ground. I noticed that you use the gravity and mass of the car to calculate it, which is good. You could simply now use this variable in the equation I provided above.

Finally, the damper variable is also a setting that shouldn’t change. Like the stiffness, just set it to something and test how it affects your behavior. It will affect how bouncy your car is. The higher the value, the less bouncy it will be.

For the two setting variables, it will take lots of tuning to get exactly how you need. It is the most important thing in a raycast suspension car!

Finally, don’t be discouraged when it doesn’t work the first couple of times. These projects are hard to debug and create in Roblox and has taken me several iterations to make a somewhat good one. To help your understanding, I found a very useful video that should explain things better than I have.

Good luck!

7 Likes

Not adding anything to your post but i wanna say it is majestic and i learned something today so well done. thank you.

2 Likes

Tried this but it doesn’t fix it:

local stiffness = 10000
local damper = 1000

local lastRatio = 0

RunService.Heartbeat:Connect(function(delta)
	for i,v in HoverParts:GetChildren() do
		if v:IsA("BasePart") then
			local VectorForce = v:WaitForChild("VectorForce")
			
			local rayOrigin = v.Position
			local rayDirection = v.CFrame.LookVector * wheelRadius
			local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
			
			if raycastResult then
				local hitPoint = raycastResult.Position
				local suspensionLength = (hitPoint - v.Position).Magnitude
				local ratio = suspensionLength / wheelRadius
				
				local stiffnessForce = (1 - ratio) * stiffness
				local damperForce = ((lastRatio - ratio) / delta) * damper
				local totalForce = stiffnessForce + damperForce
				
				VectorForce.Force = Vector3.new(0, totalForce, 0)
				
				lastRatio = ratio
			else
				VectorForce.Force = Vector3.new()
				lastRatio = 0
			end
			
			--[[
			if raycastResult then
				local normal = raycastResult.Normal
				local force = normal * (v.AssemblyMass * workspace.Gravity)
				
				VectorForce.Force = force
			else
				VectorForce.Force = Vector3.new()
			end
			]]
		end
	end
end)

1 Like

Your rayDirection is wrong. It should be -UpVector.

Code:

local stiffness = 10000
local damper = 1000

local lastRatio = 0

RunService.Heartbeat:Connect(function(delta)
	for i,v in HoverParts:GetChildren() do
		if v:IsA("BasePart") then
			local VectorForce = v:WaitForChild("VectorForce")
			
			local rayOrigin = v.Position
			local rayDirection = -v.CFrame.UpVector * wheelRadius
			local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
			
			if raycastResult then
				local hitPoint = raycastResult.Position
				local suspensionLength = (hitPoint - v.Position).Magnitude
				local ratio = suspensionLength / wheelRadius
				
				local stiffnessForce = (1 - ratio) * stiffness
				local damperForce = ((lastRatio - ratio) / delta) * damper
				local totalForce = stiffnessForce + damperForce
				
				VectorForce.Force = Vector3.new(0, totalForce, 0)
				
				lastRatio = ratio
			else
				VectorForce.Force = Vector3.zero
				lastRatio = 0
			end
		end
	end
end)

The front face is facing down:

It would only stabilize itself if I add in an AngularVelocity, but I want to balance its self without it.

local RunService = game:GetService("RunService")

local stiffness = 10000
local damper = 10000

local lastRatio = 0

RunService.Heartbeat:Connect(function(delta)
	for i,v in HoverParts:GetChildren() do
		if v:IsA("BasePart") then
			local VectorForce = v:WaitForChild("VectorForce")

			local rayOrigin = v.Position
			local rayDirection = v.CFrame.LookVector * wheelRadius
			local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

			if raycastResult then
				local hitPoint = raycastResult.Position
				local suspensionLength = (hitPoint - v.Position).Magnitude
				local ratio = suspensionLength / wheelRadius

				local stiffnessForce = (1 - ratio) * stiffness
				local damperForce = ((lastRatio - ratio) / delta) * damper
				local totalForce = stiffnessForce + damperForce

				local slowingFactor = 1 - ratio
				VectorForce.Force = Vector3.new(0, totalForce * slowingFactor, 0)

				lastRatio = ratio
			else
				VectorForce.Force = Vector3.new()
				lastRatio = 0
			end
		end
	end
end)
2 Likes

Have you tried adding more mass in the center?

2 Likes

Density is set to 100:

1 Like

Is that what you want? If not, you can just lower the density.

1 Like

Is there a better way than increasing the density?

1 Like

You could try lowering the reactionary force.

1 Like

I’ve actually fixed the problem by setting ApplyAtCenterOfMass to true.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.