Raycast suspensions

  1. I have some problems with the raycast suspensions. I have this raycast suspension system, I didn’t write it, I got help, because I tried watching tutorials and searching for tutorials, but nothing, I still can’t understand how they work.

  2. The first issue is: The suspensions don’t really work, beucase on a terrain with rocks, the car gets stuck really easily, and on a grass terrain also gets stuck when the terrain is not flat. I tried searching the solution but nothing.
    The second issue is: Whenever I exit the vehicle, all the car parts get destroyed, but the scripts and the parent models are still in the workspace (There’s no destroy script).

  3. I have tried searching the solutions and searching tutorials to learn more about raycast suspensions, but I still can’t adress the solution for these 2 problems.

Server script:

local car = script.Parent
local remoteEvent = script.Parent:WaitForChild("CarMovementEvent")
local CarUI = script.Parent:WaitForChild("CarUI")

local wheel1 = car.FrontLeft.Wheel
local wheel2 = car.FrontRight.Wheel
local wheel3 = car.BackLeft.Wheel
local wheel4 = car.BackRight.Wheel

local moveDamping = 10
local rotateDamping = 12

local suspensionHeight = 2
local suspensionLength = 5

local variablesModule = require(script.Parent:WaitForChild("GlobalVariables"))

local function smoothMove(position, targetPos, damping)
	return position + (targetPos - position) * damping
end

local function smoothRotate(rotation, targetRot, damping)
	return rotation:lerp(targetRot, damping)
end

remoteEvent.OnServerEvent:Connect(function(player, targetPos, targetRot)
	car.Position = smoothMove(car.Position, targetPos, moveDamping)
	car.Rotation = smoothRotate(car.Rotation, targetRot, rotateDamping)
end)

local function updateWheelSuspension(deltaTime, wheel)
	if not car.VehicleSeat.Occupant then return end
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {car}
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist

	local currentPosition = wheel.CFrame.Position
	local hitInfo = workspace:Raycast(wheel.Position, Vector3.new(0, -suspensionLength, 0), raycastParams)

	if hitInfo then
		local position = hitInfo.Position
		local normal = hitInfo.Normal
		local suspensionDistance = (position - currentPosition).Magnitude

		if suspensionDistance < suspensionHeight + suspensionLength then
			local targetPosition = position + normal * (suspensionHeight + suspensionLength - suspensionDistance)

			
			local springConstant = 150 
			local damping = 0.1

			local springForce = (targetPosition - currentPosition) * springConstant
			local dampingForce = wheel.Velocity * damping
			local resultantForce = springForce - dampingForce

			
			local forceMagnitude = math.min(resultantForce.Magnitude, 1000) 
			resultantForce = resultantForce.unit * forceMagnitude

			wheel:ApplyImpulse(resultantForce)
		end
	end
end

local function updateSuspensions(deltaTime)
	if not car.VehicleSeat.Occupant then return end
	updateWheelSuspension(deltaTime, wheel1)
	updateWheelSuspension(deltaTime, wheel2)
	updateWheelSuspension(deltaTime, wheel3)
	updateWheelSuspension(deltaTime, wheel4)
end

game["Run Service"].Heartbeat:Connect(updateSuspensions)

local function stopVehicleMotion()
	for _, part in pairs(car:GetDescendants()) do
		if part:IsA("BasePart") then
			part.Velocity = Vector3.new(0, 0, 0)
			part.AssemblyAngularVelocity = Vector3.new(0, 0, 0)
		end
	end
end

local function EmergencyStop()
	wheel1.WheelConstraint.AngularVelocity = 0
	wheel2.WheelConstraint.AngularVelocity = 0
	wheel3.WheelConstraint.AngularVelocity = 0
	wheel4.WheelConstraint.AngularVelocity = 0
end

local function disableWheelForces()
	for _, wheel in ipairs(car:GetChildren()) do
		if wheel:IsA("Weld") then
			wheel.Enabled = false
		end
	end
end

local function stabilizeVehicle()
	car.PrimaryPart.Anchored = true
	wait(0.1)
	car.PrimaryPart.Anchored = false
end

local minAngle,maxAngle = -130,60
local totalDegrees = maxAngle-minAngle

local gearSettings = {
	 gear1MaxSpeed = 15,
	 gear2MaxSpeed = 30,
	 gear3MaxSpeed = 45,
	 gear4MaxSpeed = 60,
	 gear5MaxSpeed = 70
}

local currentGear = "gear1"

local function calculateDegree(speed)
	local speedPerDegree = speed/gearSettings[currentGear.."MaxSpeed"]
	return math.min(minAngle+speed*speedPerDegree,maxAngle)
end

local function SpeedOmeter(player, speed)
	if speed <= -0 then
		speed = 0 - speed
	end
	local speedOmeterUI = player.PlayerGui.CarUI.Speedometer
	local deg = calculateDegree(speed)
	speedOmeterUI.ForegroundCircle.Rotation = deg
	speedOmeterUI.Speed.Text = speed.." KMH"
end

car.VehicleSeat:GetPropertyChangedSignal("Occupant"):Connect(function()
	if car.VehicleSeat.Occupant then
		local occupant = car.VehicleSeat.Occupant
		local player = game.Players:FindFirstChild(occupant.Parent.Name)
		print(occupant, occupant.Parent.Name, player)
		local carUIClone = CarUI:Clone()
		carUIClone.Parent = player:WaitForChild("PlayerGui")
	else
		EmergencyStop()
	end
end)

local seat = car.VehicleSeat
local maxSteeringAngle = 60
local steeringDamping = 0.05  
local isInNeutral = true
local neutralAngle = 0  
local steeringSensitivity = 0.05 

local function smoothSteer(steerInput, currentAngle)
	
	local targetAngle = steerInput * maxSteeringAngle

	
	local angleDifference = targetAngle - currentAngle
	local damping = angleDifference * steeringDamping

	
	currentAngle = currentAngle + damping

	
	return math.clamp(currentAngle, -maxSteeringAngle, maxSteeringAngle)
end

local function lerp(a, b, t)
	return a + (b - a) * t
end

local function updateSteering(steerInput, currentAngle)
	local speed = seat.CFrame.LookVector:Dot(seat.Velocity)
	local maxSteeringAngle = speed > 10 and 30 or 15

	
	local targetAngle = steerInput * maxSteeringAngle

	
	if steerInput == 0 then
		return lerp(currentAngle, 0, 0.2) 
	end

	
	local angleDifference = targetAngle - currentAngle
	local damping = angleDifference * steeringDamping

	
	currentAngle = currentAngle + damping

	
	return math.clamp(currentAngle, -maxSteeringAngle, maxSteeringAngle)
end

game:GetService("RunService").Heartbeat:Connect(function()
	
	if car.VehicleSeat.Occupant then
		local occupant = car.VehicleSeat.Occupant
		local player = game.Players:FindFirstChild(occupant.Parent.Name)

		SpeedOmeter(player, math.round(seat.CFrame.LookVector:Dot(seat.Velocity)))
	end
	
	local steerInput = seat.Steer
	local currentAngle1 = wheel1.Parent.PartB.SteeringConstraint.TargetAngle or 0
	local currentAngle2 = wheel2.Parent.PartB.SteeringConstraint.TargetAngle or 0

	
	local newAngle1 = updateSteering(steerInput, currentAngle1)
	local newAngle2 = updateSteering(steerInput, currentAngle2)

	
	wheel1.Parent.PartB.SteeringConstraint.TargetAngle = newAngle1
	wheel2.Parent.PartB.SteeringConstraint.TargetAngle = newAngle2
end)

local speed = 300

local currentAngularVelocity = 0 

local function smoothAngularVelocity(targetVelocity, deltaTime)
	return currentAngularVelocity + ((targetVelocity - currentAngularVelocity) * 0.1 * deltaTime) 
end

seat:GetPropertyChangedSignal("Throttle"):Connect(function()
	
	local targetAngularVelocity
	if seat.Throttle >= 1 then
		local deltaTime = game["Run Service"].Heartbeat:Wait()
		currentAngularVelocity = 1
		currentAngularVelocity = math.clamp(currentAngularVelocity + ((seat.Throttle * speed) - currentAngularVelocity) * 0.1, -speed, speed)
		wheel1.WheelConstraint.AngularVelocity = currentAngularVelocity
		wheel2.WheelConstraint.AngularVelocity = currentAngularVelocity * -1
		wheel3.WheelConstraint.AngularVelocity = currentAngularVelocity
		wheel4.WheelConstraint.AngularVelocity = currentAngularVelocity * -1
	elseif seat.Throttle == 0 then
		wheel1.WheelConstraint.AngularVelocity = 0
		wheel2.WheelConstraint.AngularVelocity = 0
		wheel3.WheelConstraint.AngularVelocity = 0
		wheel4.WheelConstraint.AngularVelocity = 0
	elseif seat.Throttle <= -1 then
		local deltaTime = game["Run Service"].Heartbeat:Wait()
		currentAngularVelocity = math.clamp(currentAngularVelocity + ((seat.Throttle * -speed) - currentAngularVelocity) * 0.1, -speed, -speed)
		wheel1.WheelConstraint.AngularVelocity = currentAngularVelocity
		wheel2.WheelConstraint.AngularVelocity = currentAngularVelocity * -1
		wheel3.WheelConstraint.AngularVelocity = currentAngularVelocity
		wheel4.WheelConstraint.AngularVelocity = currentAngularVelocity * -1
	end
end)

local function maintainWheelAttachment()
	if not car.VehicleSeat.Occupant then return end
	while true do
		if not wheel1:IsDescendantOf(car) then
			wheel1.Parent = car
		end
		if not wheel2:IsDescendantOf(car) then
			wheel2.Parent = car
		end
		if not wheel3:IsDescendantOf(car) then
			wheel3.Parent = car
		end
		if not wheel4:IsDescendantOf(car) then
			wheel4.Parent = car
		end
		wait(1) 
	end
end

task.spawn(maintainWheelAttachment)

Client script:

local car = script.Parent
local remoteEvent = script.Parent:WaitForChild("CarMovementEvent")

while true do
	if script.Parent.VehicleSeat.Occupant == nil then return end
	local targetPos = car.Position
	local targetRot = car.Rotation

	remoteEvent:FireServer(targetPos, targetRot)

	wait()
end

I hope someone can help me. Thank you.

2 Likes

Is the suspension not pushing the car up enough and it’s hitting the ground?
Do you get any errors in the Output window?

You need to fine tune the Server script to get this to work properly.
suspensionHeight and suspensionLength are the values used to set the height of the car above the ground. If you have large wheels it may affect these values.
springConstant and damping are the forces applied to the car to push the wheel down for your suspension. If your springConstant isn’t high enough for the mass of your vehicle try raising that until the car rises up. Instead of using Play to test, just have the camera on the car and press Run. This will tell you if the car chassis drops down or rises up.

The output window don’t show anything, and by changing the values I don’t see any difference.
Also, when trying to go over a part which height is 0.5, the force of the wheel isn’t enough to going over that part (if the vehicle isn’t going very fast).

Please tell me the minimum and maximum values you’ve changed the springConstant variable to.
Are you changing the values in the script then testing it in Studio, or are you trying to change the values in the script while you are testing? Changes you make to a script while testing won’t make a difference.
What is the diameter in studs of the vehicle wheels?
What is the MotorMaxForce of your wheel HingeConstraints? If it’s not high enough your hinges won’t have enough power to push the car forward.
Are the wheels spinning when you get to a bump? The Friction of the wheels might not be enough to climb up the bump.

As soon as I get on my pc I will provide all the informations. Thanks for the help.

Just skimming over the code, I see several really fundamental problems, enough that I think it’s a waste of your time to play with tuning values here and there. Your suspension code is running in a Server script, but affecting a vehicle that a player’s client is going to be simulating (that’s what you want, anyways). This alone is a huge problem because it means that even if you get something stable in Studio, it won’t work once you publish it to a real game and have actual client-server latency and you’ll be back to having no working vehicle.

But besides just the script context, I see things like the deltaTime parameter not being used (it’s of critical importance), no scaling of forces to account for mass of the vehicle (which can change with occupants, cargo, or add-ons), clamping of forces to max of 1000 which seems arbitrary and could easily be too little, and then use of BasePart:ApplyImpulse() which requires expert level knowledge of how the physics simulation works if you hope to use it as part of a stable controller. Normally you would use ApplyImpulse for one-time things like jumps or launching cannon balls, or explosions; tuning it just right to get smooth continuous motion is just asking for pain.

2 Likes

So how can I fix my script? What can I improve?

I’ve set the springConstant to 150.
I’ve changed the MotorMaxTorque of the back wheels to 483.331 (as the same as the front wheels) and now seems that the vehicle has a little bit of more force when getting over a part.
When I get to a bump the car works perfectly, but the problem is when I get to a series of a bumps one after another, where if I don’t have enough speed the car might get stucks, and the wheels don’t spin anymore.
There’s also another problem. When I get out of the vehicle, the car parts get completely destroyed, but the scripts and the remote event is still present in the workspace.

I was recommending that you not try to fix it, that you abandon this approach for now. The fastest path to a working vehicle is to build one from physics constraints.

But… if you really want to roll your own scripted suspension, you really need to start at the beginning, learning the basics of physics and control systems. Trying to get someone else’s broken code working by trial and error, without understanding what it’s trying to achieve in the first place, this is IMO an exercise in frustration that will waste time better spent on learning how to build it yourself from first principles.

483 is almost nothing compared to what the max torque should be. Try at least 10,000 to begin with and go up in steps of 5000.
Keep increasing the values until something happens. Going up in small increments is going to waste time, try much bigger numbers to see if it changes.

No, It’s 483 thousand. Not 483.

I already tried watching tutorials, but I just can’t understand anything. If you have any tip I would be glad to hear it.

Then that’s probably too high.
Before you reply back to us you HAVE to experiment with ALL the numbers I’ve mentioned. We really can’t help you if you only try 1 thing at a time when we’ve made a bunch of suggestions in each of our posts. Please understand when we say “Try these 3 suggestions” and you only try 1 of them that it makes it very hard to help you.

Start with 1 of the number variables and make it really high and test the car, then make it really low to see how that affects how the car drives. Change just that number until it works the best it can.
Move on the the next number and experiment with it the same way.
Keep going until you run out of numbers to change.
When you’re done you should get a rough idea of how changing each one affects the way the car drives.

You’ll probably have to go back through all the variables again and experiment more because many times (just like a real car) changing just 1 variable affects how all the other variables react.

After some tests with all the variables and propeties, this is what I noticed:

Setting the MotorMaxTorque at 1000 is already too much. If I set the MotorMaxTorque higher than 500 the car becomes really drifty.
Changing the damping or springConstant variables doesn’t affect the car in any way. I’ve tried setting both of the variables from 1 to 5000 to 15000 to 1000000 and even more but the car is still the same.
Also changing the 1000 in this piece of code don’t change anything: (I’ve tried 900, 90, 10000, 1000000 and even more but nothing change)

forceMagnitude = math.min(resultantForce.Magnitude, 1000)

Also changing the suspensionHeight and suspensionLength don’t change anything (I’ve tried 1.5, 0.1, 100000, 10000000 and other numbers but nothing change).

If 1000 for MotorMaxTorque is too high then you must have a very light car. This might be why you’ve got issues.
A car needs to have some mass for the suspension to work properly.
Is the car just a single Part or a MeshPart that’s very light?

I’ve never used raycast suspensions or tried to code them, so I’m just guessing as to answers to your questions. As @EmilyBendsSpace said, you’ve got some issues with your code that need to be addressed.

I’ve always used Constraints and they are pretty easy once you understand them.
Try this car I made to see what I mean:
Motor steering suspension car.rbxm (15.8 KB)
In the script I explain pretty much everything you need to understand it.

My car is a model with 45 parts (without counting the wheels and the chassis). All the parts’ massless property is set to true. The only parts with the massless property set to false are the wheels and the chassis. Anyways I’ve looked at the model, I’ve read the script and I’ve played a little bit with the propeties of the Hinge Constraints and with the Spring Constraints, to see how they affect the car.

In your car how big is the Chassis Part? If it’s small you may want to try increasing it and increasing its Density as well, just to give the car some more mass.

The chassis size is (16.802, 0.584, 6.32). I’ve changed the density of the chassis to 10, 7 and 15 (and also changed the MotorMaxTorque), but no matter what’s the density, sometimes the car shakes (not always). Here’s a the current chassis:
CarChassis1.rbxl (66.6 KB)
(I’ve removed the car model, the UI and updated the script by removing the parts of the script not in use).

@Scottifly So do you have seen my chassis? Do you have any tip on how to fix the car?

I’m not sure about how the script works. I tried messing with the forces I could see but I’d recommend going through the tutorial you mentioned.

I brought down the SuspensionLength and Height to something like 3 and 2 because you had them jacked up to insane amounts.
Your wheel hinge MaxAcceleration should be around 10 or 20, not 50000.

I didn’t get much further than that because I don’t really understand how the code is supposed to be working.

Why not just use the Roblox Constraints instead? Have a look at the car in my post here:
How can I make a car - #7 by Scottifly
Or go to my car suspension test place on my Roblox profile to see what’s possible with just Constraint suspension.

1 Like