Hey guys,
I rarely come on here for help, but I’ve been left headscratching regarding my Raycasting Suspension Module.
I understand that there’s engine limitations when accessing the internal Fixed Delta Time which poses significant issues for most raycast vehicle systems.
I’ve managed to overcome the suspension issue for the most part in my code, the only issue I am getting relating to FPS and lag is turning and forces on acceleration and deceleration.
An example of this bug:
If I sit in my vehicle and then enable my lag machine, it’ll be fine up until I start using w,a,s,d (movement controls). Upon using these controls, the car starts flinging etc.
I don’t believe this is related to the suspension of the vehicle, but the forces behind turning and speeding up.
If someone could take a look at the code below and give me some feedback, that’d be amazing.
local CollectionService = game:GetService("CollectionService")
local WheelClass = {}
WheelClass.__index = WheelClass
-- Constructor
function WheelClass.new(Vehicle, Wheel)
local self = setmetatable({}, WheelClass)
self._Vehicle = Vehicle
self._Wheel = Wheel
self._WheelAttachment = self._Vehicle._Model.PrimaryPart:FindFirstChild(Wheel.Name)
self.Metadata = {
SuspensionOutput = Vector3.zero,
TurningOutput = {X = Vector3.zero, Z = Vector3.zero},
SmoothSteer = 2,
}
return self
end
-- Private Methods
function WheelClass:_Raycast(At)
local Config = self._Vehicle._Config;
local VehiclePrimaryPart = self._Vehicle._Model.PrimaryPart;
local RayOrigin = VehiclePrimaryPart.CFrame:ToWorldSpace(CFrame.new(At)).p
local RayDirection = -VehiclePrimaryPart.CFrame.UpVector * (Config.SuspensionMaxLength + Config.wheelRadius)
local RayParams = RaycastParams.new()
RayParams.FilterType = Enum.RaycastFilterType.Exclude
RayParams.FilterDescendantsInstances = {
self._Vehicle._Model,
table.unpack(CollectionService:GetTagged("CharacterComponent"))
}
local Raycast = workspace:Raycast(RayOrigin, RayDirection, RayParams)
return Raycast
end
function WheelClass:_GetSmoothSteer(DeltaTime)
local Config = self._Vehicle._Config;
local VehiclePrimaryPart = self._Vehicle._Model.PrimaryPart;
local VehicleSeat = self._Vehicle._Model.DriveSeat;
local CurrentSmoothSteer = self.Metadata.SmoothSteer
--self.Metadata.SmoothSteer = CurrentSmoothSteer + (VehicleSeat.SteerFloat - CurrentSmoothSteer) * math.min(DeltaTime * 10, 1)
local SteerDifference = VehicleSeat.SteerFloat - CurrentSmoothSteer
local SteerChange = SteerDifference * math.min(DeltaTime * 5, 1)
self.Metadata.SmoothSteer = CurrentSmoothSteer + SteerChange
self.Metadata.SmoothSteer = math.clamp(self.Metadata.SmoothSteer, -1, 1)
end
function WheelClass:_GetSuspension(Raycast)
local Config = self._Vehicle._Config;
local VehiclePrimaryPart = self._Vehicle._Model.PrimaryPart;
local VehicleSeat = self._Vehicle._Model.DriveSeat;
local SpringLength = math.clamp(Raycast.Distance - Config.wheelRadius, 0, Config.SuspensionMaxLength)
local SpringDir = VehiclePrimaryPart.CFrame.UpVector
local TireWorldVelocity = VehiclePrimaryPart:GetVelocityAtPosition(Raycast.Position)
local Offset = Config.SuspensionMaxLength - SpringLength
local Vel = TireWorldVelocity:Dot(SpringDir)
local Force = (Offset * Config.Stiffness) - (Vel * Config.Damper)
local OutputForce = (SpringDir * Force)
self.Metadata.SuspensionOutput = OutputForce
return self.Metadata.SuspensionOutput
end
function WheelClass:_GetTurning(Raycast)
local Config = self._Vehicle._Config;
local VehiclePrimaryPart = self._Vehicle._Model.PrimaryPart;
local VehicleSeat = self._Vehicle._Model.DriveSeat;
local CarCFrame = VehiclePrimaryPart.CFrame
local RotationsOnlyWheelDirCFrame = CFrame.lookAt(Vector3.zero, CarCFrame.LookVector, CarCFrame.UpVector)
if string.sub(self._Wheel.Name, 1, 1) == 'F' then
RotationsOnlyWheelDirCFrame = RotationsOnlyWheelDirCFrame * CFrame.Angles(0, -math.rad(self.Metadata.SmoothSteer * Config.SteerAngle), 0)
end
local LocalVelocity = RotationsOnlyWheelDirCFrame:ToObjectSpace(CFrame.new(VehiclePrimaryPart:GetVelocityAtPosition(Raycast.Position)))
local Xforce = RotationsOnlyWheelDirCFrame.RightVector * -LocalVelocity.x * Config.wheelFriction
local Zforce = RotationsOnlyWheelDirCFrame.LookVector * VehicleSeat.ThrottleFloat * Config.torque * (math.sign(-LocalVelocity.z) == VehicleSeat.Throttle and (1 - math.min(1, math.abs(LocalVelocity.z) / Config.MaxSpeed)) or 1)
self.Metadata.TurningOutput = {
X = Vector3.new(Xforce.X, 0, Xforce.Z),
Z = Vector3.new(Zforce.X, 0, Zforce.Z),
}
return self.Metadata.TurningOutput
end
-- Public Methods
function WheelClass:Update(DeltaTime)
local Raycast = self:_Raycast(self._WheelAttachment.Position)
if Raycast then
self:_GetSmoothSteer(DeltaTime)
local SuspensionForce = self:_GetSuspension(Raycast)
local TurningForce = self:_GetTurning(Raycast)
self._WheelAttachment.SpringForce.Force = (SuspensionForce + ((TurningForce.X + TurningForce.Z) * 40))
else
self._WheelAttachment.SpringForce.Force = Vector3.zero
end
end
function WheelClass:Destroy()
end
return WheelClass