Adding friction to raycast/spherecast car suspension

Hello! Ive been making a spherecast / raycast car suspension system and Ive been running into a problem. The car suspension works perfectly except the fact that the car moves like its on ice. I figure it needs friction but Im not sure how to implement this to my current math so I was wondering if anyone could help me with this.

This is my suspension module code:

-- Services
local replicatedStorage = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")

-- Modules
local debugEffects = require(replicatedStorage.Effects.Debug)

-- Module
local suspension = {}
suspension.__index = suspension

local debugEffect

-- Functions
local function absoluteVector(vector : Vector3)
	return Vector3.new(math.abs(vector.X), math.abs(vector.Y), math.abs(vector.Z))
end

function suspension.new(vehicle : Model, wheelAttachment : Attachment)
	local self = setmetatable({}, suspension)
	
	self.vehicle = vehicle
	self.wheel = wheelAttachment
	self.wheelPart = self.vehicle.Wheels[self.wheel.Name]
	self.vectorForce = self.wheel.VectorForce
	
	self.restLength = self.wheelPart:GetAttribute("RestLength")
	self.wheelRadius = self.wheelPart:GetAttribute("WheelRadius")
	self.springStiffness = self.wheelPart:GetAttribute("SpringStiffness")
	self.springTravel = self.wheelPart:GetAttribute("SpringTravel")
	self.damperStiffness = self.wheelPart:GetAttribute("DamperStiffness")
	
	self.minLength = (self.restLength - self.springTravel)
	self.maxLength = (self.restLength + self.springTravel)
	
	self.lastLength = self.wheelRadius
	self.spherecast = nil
	
	self.debug = true
	
	if self.debug then
		self.debugOrigin = self.vehicle.Debug.Origin[string.gsub(self.wheel.Name, "Wheel", "Origin")]
		self.debugEnd = self.vehicle.Debug.End[string.gsub(self.wheel.Name, "Wheel", "End")]
		self.debugSphere = self.vehicle.Debug.Spheres[string.gsub(self.wheel.Name, "Wheel", "Sphere")]
		self.debugRay = self.debugOrigin.Beam
	end
	
	self.raycastParams = RaycastParams.new()
	self.raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	self.raycastParams.FilterDescendantsInstances = {self.vehicle}
	
	return self
end

function suspension:update(deltaTime : number)
	self:updateProperties()
	self:calculatePhysics(deltaTime)
	self:applyForce()
end

function suspension:updateProperties()
	self.restLength = self.wheelPart:GetAttribute("RestLength")
	self.wheelRadius = self.wheelPart:GetAttribute("WheelRadius")
	self.springStiffness = self.wheelPart:GetAttribute("SpringStiffness")
	self.springTravel = self.wheelPart:GetAttribute("SpringTravel")
	self.damperStiffness = self.wheelPart:GetAttribute("DamperStiffness")
end

function suspension:calculatePhysics(deltaTime : number)
	self.spherecast = workspace:Spherecast(self.wheel.WorldPosition, self.wheelRadius / 2, -self.vehicle.PrimaryPart.CFrame.UpVector * self.wheelRadius, self.raycastParams)

	if self.spherecast then
		self:updateDebug(self.wheel.WorldPosition)
		
		local extension = self.wheelRadius - self.spherecast.Distance
		local relativeVelocity = self.vehicle.PrimaryPart.CFrame:VectorToObjectSpace(self.wheelPart.Velocity)
		
		self.springLength = (self.spherecast.Distance - self.wheelRadius)
		local springLength = math.clamp(self.springLength, self.minLength, self.maxLength)
		self.springVelocity = (self.lastLength - self.springLength) / deltaTime
		self.springForce = self.vehicle.PrimaryPart.CFrame.UpVector * (self.springStiffness * springLength - self.damperStiffness * relativeVelocity.Y)
		self.damperForce = self.damperStiffness * self.springVelocity
		
		self.suspensionForce = (Vector3.new(0, self.springForce, 0) + Vector3.new(0, self.damperForce, 0))
		
		self.lastLength = self.springLength
	end
end

function suspension:updateDebug(origin : Vector3)
	if not self.debug then return end
	self.raycast = workspace:Raycast(self.wheel.WorldPosition, -self.vehicle.PrimaryPart.CFrame.UpVector * self.wheelRadius * 100, self.raycastParams)
	
	if self.spherecast and self.raycast then
		local distance = (origin - self.raycast.Position)
		local position = Vector3.new(origin.X, distance.Y, origin.Z)
		
		self.debugRay.Color = ColorSequence.new(Color3.fromRGB(255, 220, 23))

		self.debugOrigin.Position = origin
		self.debugEnd.Position = self.raycast.Position
		
		local radius = self.wheelRadius / 2
		self.debugSphere.Position = self.raycast.Position + Vector3.new(0, radius, 0)
		self.debugSphere.Size = Vector3.new(radius * 2, radius * 2, radius * 2)
		
		self.debugOrigin.Orientation = Vector3.zero
		self.debugEnd.Orientation = Vector3.zero
		
	elseif not self.raycast or self.spherecast then
		self.debugRay.Color = ColorSequence.new(Color3.fromRGB(73, 255, 7))
	end
end

function suspension:applyForce()
	if not self.spherecast then self.vectorForce.Force = Vector3.zero return end
	
	self.vectorForce.Force = self.suspensionForce
end

-- Returning
return suspension

This code is ran on the server in a loop for now.
Heres an example of the car skating:

External Media
1 Like