Hello, I’ve been working on a NPC car and when things were going well I noticed that after 30 - 60 seconds in game the fps starts to decrease, I know that this happens because every frame I start some while loops, but they break after some conditions gets false, I don’t see why broken loops would cause lag.
Function that runs the loops:
RunService.Heartbeat:Connect( function()
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
local RaycastResult = game.Workspace:Raycast(
rayOrigin.Position,
rayOrigin.CFrame.LookVector * 10,
raycastParams
)
if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then
canSpeedUp = false
canBrake = true
while canBrake do
if slowDown(throttle, 0.02) == false then break end
task.wait(0.02)
end
else
canBrake = false
canSpeedUp = true
while canSpeedUp and throttle.Value < maxThrottle do
if canBrake == false then
speedUp(throttle)
end
task.wait(0.25)
end
end
for _, child in pairs(car:GetChildren()) do
if child:IsA("BasePart") then
local connection = child.Touched:Connect(function() end)
for _, part in ipairs(child:GetTouchingParts()) do
recoverSteer(part)
end
elseif child:IsA("Folder") then
for _, folderChild in pairs(child:GetChildren()) do
local connection = folderChild.Touched:Connect(function() end)
for _, part in ipairs(folderChild:GetTouchingParts()) do
recoverSteer(part)
end
end
end
end
if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) > 0.001 and turning == false then
alingSteer(desiredSteer, 0.4)
elseif turning == false then
ChangeSteer:Fire(0)
end
end)
If the loops really can’t stay in the code what should i use to substitute them? I tried to remove the loops but this makes who got better fps accelerate faster.
There are a couple of things here you shouldn’t be doing.
Remove the while loops from the code.
Heartbeat updates every frame, meaning every frame a new while loop is added to the computation. You should make some simple if then checks for speedUp() and slowDown().
EX:
if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then
canSpeedUp = false
canBrake = true
slowDown(throttle, 0.02)
else
canBrake = false
canSpeedUp = true
if throttle.Value < maxThrottle then
speedUp(throttle)
end
end
end
You are making new touched connections every frame.
You should make the touch connections outside of RunService.Heartbeat, we can save memory here.
for _, child in pairs(car:GetChildren()) do
if child:IsA("BasePart") then
local connection = child.Touched:Connect(function() end)
elseif child:IsA("Folder") then
for _, folderChild in pairs(child:GetChildren()) do
local connection = folderChild.Touched:Connect(function() end)
end
end
end
end
RunService.Heartbeat:Connect(function()
end)
It is a bad idea to place wait (task.wait()) inside RunServer (and worse inside a loop as in your case). You can take those loops out in coroutines.
Creating connections at every step of the game is another very bad idea. This means a huge memory leak. Try to do it outside or be very careful when you do it inside RunServer.
I hope this has been helpful.
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
local canBrake
local co1 = coroutine.create(function ()
while canBrake do
if slowDown(throttle, 0.02) == false then break end
task.wait(0.02)
end
end)
local canSpeedUp
local co2 = coroutine.create(function()
while canSpeedUp and throttle.Value < maxThrottle do
if canBrake == false then
speedUp(throttle)
end
task.wait(0.25)
end
end)
RunService.Heartbeat:Connect( function()
local RaycastResult = game.Workspace:Raycast(
rayOrigin.Position,
rayOrigin.CFrame.LookVector * 10,
raycastParams
)
if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then
canSpeedUp = false
canBrake = true
coroutine.resume(co1)
else
canBrake = false
canSpeedUp = true
coroutine.resume(co2)
end
for _, child in pairs(car:GetChildren()) do
if child:IsA("BasePart") then
for _, part in ipairs(child:GetTouchingParts()) do
recoverSteer(part)
end
elseif child:IsA("Folder") then
for _, folderChild in pairs(child:GetChildren()) do
for _, part in ipairs(folderChild:GetTouchingParts()) do
recoverSteer(part)
end
end
end
end
if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) > 0.001 and turning == false then
alingSteer(desiredSteer, 0.4)
elseif turning == false then
ChangeSteer:Fire(0)
end
end)
for _, child in pairs(car:GetChildren()) do
if child:IsA("BasePart") then
local connection = child.Touched:Connect(function() end)
elseif child:IsA("Folder") then
for _, folderChild in pairs(child:GetChildren()) do
local connection = folderChild.Touched:Connect(function() end)
end
end
end
I know about that, if i remove the whiles then acceleration will be every frame, so a player with 30 fps will speed up slower than a 60 fps player:
–
Maybe this is the cause of the lag, because the whiles break after the variables change, I’ll try to move the for loops to outside the heartbeat, thank you for the answer.
I removed the for loops and I noticed that they were inside the heatbeat because they’re not basePart.Touched:Connect() they use :GetTouchingParts, I need to run this every frame otherwise I’ll get the touching parts only when I runned the loop.
It’s an npc car, i know i could make this with pathfinding but i made it with constraints, here’s the full script:
-- Services
local RunService = game:GetService("RunService")
-- Bindable events
local ChangeThrottle = game.ReplicatedStorage.BindableEvents.Car.ChangeThrottle
local ChangeSteer = game.ReplicatedStorage.BindableEvents.Car.ChangeSteer
-- Configuration
local maxThrottle = 1
local maxReverseThrottle = -0.7
-- Variables
local car = script.Parent
local rayOrigin = car.RayOrigin
local desiredSteer = math.round(rayOrigin.CFrame.LookVector.Z)
local configuration = car.Configuration
local throttle = configuration.Throttle
local canSpeedUp = false
local canBrake = false
local turning = false
-- Functions
local function speedUp(throttle)
if throttle.Value >= 0 then -- Speed up
ChangeThrottle:Fire(0.05)
else -- Brake
ChangeThrottle:Fire(0.1)
end
end
local function slowDown(throttle, intensity)
if throttle.Value > 0 then
ChangeThrottle:Fire(-intensity)
else
ChangeThrottle:Fire(intensity)
end
if throttle.Value <= intensity and throttle.Value >= -intensity then
ChangeThrottle:Fire(0)
return false
else
return true
end
end
local function alingSteer(desiredSteer, sensibility)
if desiredSteer ~= 0 then
if rayOrigin.CFrame.LookVector.Z < desiredSteer then
ChangeSteer:Fire(-sensibility)
else
ChangeSteer:Fire(sensibility)
end
elseif desiredSteer == 0 then
if rayOrigin.CFrame.LookVector.Z < desiredSteer then
ChangeSteer:Fire(sensibility)
else
ChangeSteer:Fire(-sensibility)
end
end
end
local function steerTo(direction, turnWaitTime, recoveringSteer)
turning = true
ChangeSteer:Fire(direction)
task.wait(turnWaitTime)
if recoveringSteer == nil then
desiredSteer = math.round(rayOrigin.CFrame.LookVector.Z)
end
while true do
task.wait(0.05)
alingSteer(desiredSteer, 0.4)
if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) < 0.5 then
turning = false
break
end
end
end
local function randomizeSteer(touch)
local randomizer = math.random(-1, 1)
if randomizer ~= 0 then
if randomizer == 1 then
steerTo(1, touch:GetAttribute("RightTurnWait"))
elseif randomizer == -1 then
steerTo(touch:GetAttribute("LeftTurnDirection"), touch:GetAttribute("LeftTurnWait"))
end
return true
else
return false
end
end
local function recoverSteer(touch)
if touch.Name == "SteerRecover" and turning == false then
steerTo(touch:GetAttribute("Direction"), 0.05, true)
end
end
-- Event functions
car.Structures.MainStructure.Touched:Connect( function(touch)
if touch.Name == "Turn" and touch:GetAttribute("Obrigatory") == false then
local turnChance = math.random(0, 1)
if turnChance > 0 then
if touch:GetAttribute("Direction") == 0 then
randomizeSteer(touch)
else
steerTo(touch:GetAttribute("Direction"), touch:GetAttribute("TurnWaitTime"))
end
end
elseif touch.Name == "Turn" then
if touch:GetAttribute("Direction") == 0 then
while true do
task.wait(0.1)
if randomizeSteer(touch) then
break
end
end
else
steerTo(touch:GetAttribute("Direction"), touch:GetAttribute("TurnWaitTime"))
end
end
end)
for _, child in pairs(car:GetChildren()) do
if child:IsA("BasePart") then
local connection = child.Touched:Connect(function() end)
for _, part in ipairs(child:GetTouchingParts()) do
recoverSteer(part)
end
elseif child:IsA("Folder") then
for _, folderChild in pairs(child:GetChildren()) do
local connection = folderChild.Touched:Connect(function() end)
for _, part in ipairs(folderChild:GetTouchingParts()) do
recoverSteer(part)
end
end
end
end
RunService.Heartbeat:Connect( function()
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
local RaycastResult = game.Workspace:Raycast(
rayOrigin.Position,
rayOrigin.CFrame.LookVector * 10,
raycastParams
)
if RaycastResult and RaycastResult.Normal.Y < 1 and RaycastResult.Instance.CanCollide == true then
canSpeedUp = false
canBrake = true
if canBrake then
slowDown(throttle, 0.02)
task.wait(0.02)
end
else
canBrake = false
canSpeedUp = true
if canSpeedUp and throttle.Value < maxThrottle then
if canBrake == false then
speedUp(throttle)
end
task.wait(0.25)
end
end
if math.abs(rayOrigin.CFrame.LookVector.Z - desiredSteer) > 0.001 and turning == false then
alingSteer(desiredSteer, 0.4)
elseif turning == false then
ChangeSteer:Fire(0)
end
end)
All of the work is already done for you in the physics engine! It’s as simple as setting reasonable values for the constraints, and then setting values in a Script. No interpolation should be needed.
Additionally, you shouldn’t be firing remotes every frame. Let the server interpolate things like this instead of the client.
So if i set throttle to 1 the car will speed up smoothly?
Heres the script that recieves the fires:
-- Services
local Players = game:GetService("Players")
-- Bindable events
local ChangeThrottle = game.ReplicatedStorage.BindableEvents.Car.ChangeThrottle
local ChangeSteer = game.ReplicatedStorage.BindableEvents.Car.ChangeSteer
-- Variables
local car = script.Parent
local seat = car.Seat
local configuration = car.Configuration
local throttle = configuration.Throttle
local steer = configuration.Steer
local motor1 = car.Structures.WheelMount.Cylindrical1
local motor2 = car.Structures.WheelMount2.Cylindrical2
local motor3 = car.Structures.SteerPart.Cylindrical3
local motor4 = car.Structures.SteerPart2.Cylindrical4
local steerServo1 = car.Structures.SteerPart.SteerHinge1
local steerServo2 = car.Structures.SteerPart2.SteerHinge2
local maxSpeed = 10
local steerAngle = 40
local wheelFriction = 3
local player
-- Utility functions
local function changeWheelsPhysics(density, friction, elasticity, frictionWeight, elasticityWeight)
for _, wheel in pairs(car.Wheels:GetChildren()) do
local CustomPhysics = PhysicalProperties.new(density, friction, elasticity, frictionWeight, elasticityWeight)
wheel.CustomPhysicalProperties = CustomPhysics
end
end
local function setMotorVelocity(velocity)
motor1.AngularVelocity = -velocity
motor2.AngularVelocity = velocity
motor3.AngularVelocity = -velocity
motor4.AngularVelocity = velocity
end
-- Event functions
ChangeThrottle.Event:Connect( function(increment)
throttle.Value = throttle.Value + increment
if increment == 0 then
throttle.Value = 0
end
setMotorVelocity(throttle.Value * maxSpeed)
end)
ChangeSteer.Event:Connect( function(desiredSteer)
steer.Value = desiredSteer
steerServo1.TargetAngle = steerAngle * steer.Value
steerServo2.TargetAngle = steerAngle * steer.Value
end)
-- Tasks
changeWheelsPhysics(1, 3, 1, 1, 1)
Well, sorry, i didn’t notice that you removed only the child.Touched:Connect(function() end) from the heartbeat, and the whiles didn’t caused any lag i don’t know why.
If you set it to anything it should accelerate smoothly towards your target, provided you set up the constraints right.
If you set MotorMaxAcceleration to a stupidly high number then it will try it’s absolute hardest to reach your target speed, and if you also set MotorMaxTorque to a stupidly high number it’ll immediately reach your target speed.