I’m working on my raycast car suspension and its going sort of fine, but I’m facing 2 issues that I’m not sure how to fix, 1st is that the backwheels are the ones that are visibly turning (the car itself turns, the wheels are for show) but I need the front wheels to be turning. 2nd issue is a very big issue, when the car flips over, it violently starts to spin and go airbourne. Any help would be appreciated!
Video of Issues:
Wheels:
Suspension Problem:
Server Script:
local carModel = game.ReplicatedStorage.CarModel
local WheelPositions = {
FL = Vector3.new(-2.5, 0, -4.75),
FR = Vector3.new(2.5, 0, -4.75),
RL = Vector3.new(-2.5, 0, 4.75),
RR = Vector3.new(2.5, 0, 4.75)
}
local WheelModel = game.ReplicatedStorage.Wheel
local carProps = require(game.ReplicatedStorage:WaitForChild("carPropertiesModule"))
local WheelSuspensionLengthMemory = {}
for i,v in pairs(WheelPositions) do WheelSuspensionLengthMemory[i] = 0.5 end
local VehicleFolder = workspace.Vehicles
local AllCarsMemory = {}
local ExistingCarsTable = {}
local function AddCar(obj,cprops)
table.insert(ExistingCarsTable,obj)
obj.PrimaryPart:SetNetworkOwner(nil)
AllCarsMemory[obj] = {}
AllCarsMemory[obj].SpringLengthMemory = {}
for i,v in pairs(cprops.WheelPositions)do AllCarsMemory[obj].SpringLengthMemory[i] = 0.5 end
obj:FindFirstChildOfClass('VehicleSeat'):GetPropertyChangedSignal('Occupant'):Connect(function()
local occ = obj:FindFirstChildOfClass('VehicleSeat').Occupant
if occ then
local player = game.Players:GetPlayerFromCharacter(occ.Parent)
if player then
obj.PrimaryPart:SetNetworkOwner(player)
end
else
obj.PrimaryPart:SetNetworkOwner(nil)
end
end)
for i,v in pairs(obj:GetChildren())do
if v:IsA("Seat")then
v:GetPropertyChangedSignal('Occupant'):Connect(function()
if v.Occupant then
local player = game.Players:GetPlayerFromCharacter(v.Occupant.Parent)
if player then
wait()
if obj.PrimaryPart:GetNetworkOwner()==player then
obj.PrimaryPart:SetNetworkOwner(nil)
end
end
end
end)
end
end
--wheels
local WheelFolder = Instance.new("Folder",obj)
WheelFolder.Name = 'Wheels'
for i,v in pairs(cprops.WheelPositions)do
local ClonedWheel = WheelModel:Clone()
ClonedWheel.Parent = WheelFolder
ClonedWheel.Name = i
ClonedWheel.CanCollide = true
local weld = Instance.new("Weld",ClonedWheel)
weld.Part0 = obj.PrimaryPart
weld.Part1 = ClonedWheel
weld.C0 = CFrame.new(cprops.WheelPositions[i])
if v.x>0 then weld.C0 = weld.C0 * CFrame.Angles(0,math.rad(180),0)end
end
end
for _,object in pairs(VehicleFolder:GetChildren())do
AddCar(object)
end
local function SpawnCar(carName)
local props = carProps[carName]
if props then
local clonedCar = game.ReplicatedStorage.CarModel:Clone()
clonedCar.Parent = workspace.Vehicles
clonedCar:MoveTo(Vector3.new(math.random(1,30)-15,13,math.random(1,30)-15))
clonedCar:SetAttribute("id",carName)
AddCar(clonedCar,props)
end
end
local WhatPlayerCarTab = {}
game.Players.PlayerAdded:Connect(function(p)
local isP = p.MembershipType == Enum.MembershipType.Premium
p.CharacterAdded:Connect(function(c)
local nc = SpawnCar("CarModel")
WhatPlayerCarTab[p] = nc
end)
end)
local SuspensionMaxLength = 1.3
local WheelRadius = 0.5
local Stiffness = 100
local Damper = 5
local wheelFriction = 5
while true do
local delta = game["Run Service"].Heartbeat:Wait()
for _, carModel in pairs(ExistingCarsTable) do
local carPrim = carModel.PrimaryPart
for wheelName, originalPosition in pairs(WheelPositions) do
local carCFrame = carPrim.CFrame
local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(originalPosition)).Position
local rayDirection = -carCFrame.UpVector * (SuspensionMaxLength + 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 - WheelRadius, 0, SuspensionMaxLength)
local StiffnessForce = Stiffness * (SuspensionMaxLength - SpringLength)
local DamperForce = Damper * ((WheelSuspensionLengthMemory[wheelName] - SpringLength) / delta)
local SuspensionForceVec3 = carCFrame.UpVector * (StiffnessForce + DamperForce)
local RotationOnlyWheelDirCFrame = CFrame.lookAt(Vector3.zero, carCFrame.LookVector,carCFrame.UpVector)
local LocalVelocity = RotationOnlyWheelDirCFrame:ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))
local Xforce = carCFrame.RightVector * -LocalVelocity.X * wheelFriction
WheelSuspensionLengthMemory[wheelName] = SpringLength
carPrim:ApplyImpulseAtPosition(SuspensionForceVec3 + Xforce, raycast.Position)
else
WheelSuspensionLengthMemory[wheelName] = SuspensionMaxLength
end
end
end
end
LocalScript:
local char = script.Parent
local humanoid = char:WaitForChild("Humanoid")
local WheelPositions = {
FL = Vector3.new(-2.5, 0, -4.75),
FR = Vector3.new(2.5, 0, -4.75),
RL = Vector3.new(-2.5, 0, 4.75),
RR = Vector3.new(2.5, 0, 4.75)
}
local WheelSuspensionLengthMemory = {}
for i,v in pairs(WheelPositions) do WheelSuspensionLengthMemory[i] = 0.5 end
local carProps = require(game.ReplicatedStorage:WaitForChild("carPropertiesModule"))
local SuspensionMaxLength = 1.4
local WheelRadius = 1.5
local Stiffness = 120
local Damper = 5
local wheelFriction = 0.7
local torque = 30
local MaxSpeed = 200
local SteerAngle = 30
local SmoothSteer = 0
humanoid.Seated:Connect(function(seated, seatPart)
if seated and seatPart then
if seatPart:FindFirstAncestor("Vehicles") and seatPart:IsA("VehicleSeat") then
local carModel = seatPart.Parent
local carPrim = carModel.PrimaryPart
local serverTakeOverWait = 0
while true do
local delta = game["Run Service"].Heartbeat:Wait()
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(WheelPositions) do
local carCFrame = carPrim.CFrame
local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(originalPosition)).Position
local rayDirection = -carCFrame.UpVector * (SuspensionMaxLength + WheelRadius)
local rayParams = RaycastParams.new()
local raycast = workspace:Raycast(rayOrigin, rayDirection, rayParams)
if raycast then
local RaycastDistance = (rayOrigin - raycast.Position).Magnitude
local SpringLength = math.clamp(RaycastDistance - WheelRadius, 0, SuspensionMaxLength)
local StiffnessForce = Stiffness * (SuspensionMaxLength - SpringLength)
local DamperForce = Damper * ((WheelSuspensionLengthMemory[wheelName] - SpringLength) / delta)
local SuspensionForceVec3 = carCFrame.UpVector * (StiffnessForce + DamperForce)
local RotationOnlyWheelDirCFrame = CFrame.lookAt(Vector3.zero, carCFrame.LookVector,carCFrame.UpVector) * CFrame.Angles(0, -math.rad(SmoothSteer * SteerAngle), 0)
if string.sub(wheelName,1,1) == "F" then
RotationOnlyWheelDirCFrame = RotationOnlyWheelDirCFrame * CFrame.Angles(0, math.rad(SmoothSteer * SteerAngle), 0)
end
local LocalVelocity = RotationOnlyWheelDirCFrame:ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))
local Xforce = RotationOnlyWheelDirCFrame.RightVector * -LocalVelocity.X * wheelFriction
local Zforce = RotationOnlyWheelDirCFrame.LookVector * -seatPart.ThrottleFloat * torque * (1 - math.min(1, math.abs(LocalVelocity.Z)/MaxSpeed))
WheelSuspensionLengthMemory[wheelName] = SpringLength
carPrim:ApplyImpulseAtPosition(SuspensionForceVec3 + Xforce + Zforce, raycast.Position)
else
WheelSuspensionLengthMemory[wheelName] = SuspensionMaxLength
end
end
end
end
end
end)
local WheelRotations = {}
game["Run Service"]:BindToRenderStep('MoveWheels',5,function(delta)
for _,vehicle in pairs(workspace.Vehicles:GetChildren())do
local carPrim = vehicle.PrimaryPart
local id = vehicle:GetAttribute("id")
local props = carProps[id]
for i,v in pairs(WheelPositions)do
local carCFrame = carPrim.CFrame
local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(Vector3.new(v.x,v.y - WheelRadius,v.z))).p
local rayDirection = -carCFrame.UpVector * SuspensionMaxLength
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {vehicle}
local raycast = workspace:Raycast(rayOrigin,rayDirection,rayParams)
if raycast then
local DatWeld = vehicle.Wheels[i].Weld
DatWeld.C0 = CFrame.new(v - Vector3.new(0, (rayOrigin - raycast.Position).magnitude, 0))
if v.X > 0 then DatWeld.C0 = DatWeld.C0 * CFrame.Angles(0, math.rad(180), 0) end
if string.sub(i, 1, 1) == "F" then
DatWeld.C0 = DatWeld.C0 * CFrame.Angles(0, -math.rad(SmoothSteer * SteerAngle), 0)
end
local LocalVelocity = CFrame.lookAt(Vector3.zero,carCFrame.LookVector,carCFrame.UpVector):ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))
if not WheelRotations[vehicle] then
WheelRotations[vehicle] = {}
for i,v in pairs(WheelPositions) do WheelRotations[vehicle][i] = 0.5 end
end
WheelRotations[vehicle][i] += LocalVelocity.Z/56
DatWeld.C0 = DatWeld.C0 * CFrame.Angles(WheelRotations[vehicle][i], 0, 0)
end
end
end
end)