Local StarterCharacterScript causes intense lag, on all devices

BACKGROUND INFO. I have this script. What this script does is “Help a player stay on a tweening part”

How it works: It “raycasts” below the character’s leg too check for a part called “TrainFloor” by ignoring all other parts.

If a TrainFloor is detected, that means the Player is standing on the train and moves the Players CFrame relative to the trains.

ISSUE:
The script is causing intense lag. When I enabled this script and ran it on different devices on different accounts, it dropped my FPS from a smooth 60 to 15-10 FPS.

When disabled, everything runs smoothly.

*Listing devices specifications at the end

Here is the script. (Game uses R6 Avatars)

local Players = game:GetService("Players")
local player = game.Players.LocalPlayer
local RunService = game:GetService('RunService')

local LastTrainCFrame

local Function
local Function2

local TRAIN_BASE_NAME = "TrainFloor"

local DoOnce = false
local LastHit = nil


local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

Function = RunService.Heartbeat:Connect(function()
	local RootPart = player.Character:WaitForChild("Right Leg")
	local Ignore = { player.Character }
	
	for _, part in game.workspace:GetDescendants() do
		if part.Name ~= "TrainFloor" and part.Parent.Name ~= "Workspace" then
			table.insert(Ignore, part)
		end
	end

	raycastParams.FilterDescendantsInstances = Ignore

	local result = workspace:Raycast(RootPart.CFrame.p, Vector3.new(0, -500, 0), raycastParams)
	if not result then
		return --nothing below the character
	end

	local Hit, Position, Normal, Material = result.Instance, result.Position, result.Normal, result.Material
	
	--------------------------------
	--print(Hit.Name)
	
	if DoOnce == false then
		DoOnce = true
		LastHit = Hit
	end
	
	if LastHit == Hit then
		--Do Nothing
	elseif LastHit ~= Hit then
		LastHit = Hit
		LastTrainCFrame = nil
	end
	

	local Train = nil
	local TrainCF = nil
	local Rel = nil
	
	
	if Hit and player.Character.Humanoid.Sit == false then
		--print("slay")
		-- MOVE PLAYER TO NEW POSITON FROM OLD POSITION:
		Train = Hit
		if LastTrainCFrame == nil then -- If no LastTrainCFrame exists, make one!
			LastTrainCFrame = Train.CFrame -- This is updated later.
		end

		TrainCF = Train.CFrame 
		Rel = TrainCF * LastTrainCFrame:inverse()
		LastTrainCFrame = Train.CFrame -- Updated here.
		RootPart.CFrame = Rel * RootPart.CFrame -- Set the player's CFrame
	else
		LastTrainCFrame = nil
		Train = nil
		TrainCF = nil
		Rel = nil
	end

	Function2 = player.Character.Humanoid.Died:Connect(function()
		Function:Disconnect() -- Stop memory leaks
		Function2:Disconnect() -- Stop memory leaks
	end)
end)

Here is the script. There are no errors in output. The script works as intended.

Devices specifications
Tested on computer: Has an i7 processor, Laptop
Tested on Phone: Samsung

Any help is appreciated. Please ask for more information and I will give you it :grin:

I have been working on this game for 6 months, and this is stopping me for progressing further. Any help is appreciated :slight_smile:

1 Like

“The Heartbeat event fires every frame , after the physics simulation has completed.” Source: RunService | Documentation - Roblox Creator Hub

Could you set RootPart and Ignore above of the function instead of in it? Those variables are being made every frame, when I’m assuming it doesn’t need to be.


The code above, in context not entirely shown, is getting every descendant in the workspace every frame. That’s probably the main thing causing lag.

Instead of a blacklist, looking to ignore parts, you should change the raycast to use a whitelist, looking for a specific part/s. So, don’t Ignore parts for raycastParams.FilterDescendantsInstances.

https://create.roblox.com/docs/reference/engine/enums/RaycastFilterType

Instead of not named “trainfloor”, looking for something named that would be better I think. But, looking at all descendants of workspace is a bad idea either way. Not sure the best alternative right now.


I’m sure there’s other parts of the code that could be changed, but I think those are the main things.

2 Likes

Thanks for your help :smiley: I will get back to you asap if your suggestions worked!

2 Likes

RunService.Heartbeat event fires around 240 times per second.
Try RunService.Stepped, that is around 60 times per second as it also syncs with the games FPS rate.

2 Likes

Will try that suggestion as well. :smiley:

1 Like

Thank you so much. It worked. Frames have increased so much. Tanks! :grin:

1 Like

I caution assuming player frame rates in code. Doing so could cause more issues. Based on the documentation, both events fire based upon the user’s hardware and the frames that user can reach.

“As Stepped fires every frame, it runs on a variable frequency. This means the rate will vary depending on the performance of the machine. If the game is running at 40 FPS, then Stepped will fire 40 times per second and the step argument will be roughly 1/40th of a second.”

Source: https://create.roblox.com/docs/reference/engine/classes/RunService#Stepped

So should I use stepped or heartbeat in this situation

Pretty much what I just said … “it syncs with the game’s FPS”. That is just what you want a loop wait to do.

1 Like

I have a question, since Stepped based on player frames. So, If I have 1 player running his game at 60 FPS and the other at 6 FPS, the guy with 6 FPS will have his code run at a slower interval, this his position updates slower than the guy at 60fps.

1 Like

Either works, it doesn’t matter much I think. I’m not sure which would be “better”.

My gut says heartbeat, cause a player could jump in the train, affecting physics, which could alter things. I’d test it out and see which feels better for your use case. :person_shrugging:

1 Like

Yes, but Roblox handles some things related to that on the multiplayer side of things. Those with higher FPS will get more frequent updates on their position and smoother gameplay overall.

Final question, this is unrelated, but the code updates the players position as said in the above.

However it is a local script, does that mean it only updates the players position on the client or does it also get replicated to the server so other people can see his position updating

In short, yes, Roblox handles it by default.

CFrames and positions on the client get updated/sent to the server, which are also sent/interpreted by other clients/users. If a player has low frame rates, then those updates to the client from the server on Cframes/positions of others won’t come in as “fast”.

1 Like

Alright Thanks for all the help :smiley:

1 Like

I know this has already been solved but I wanted to point out that you’re making a connection to Humanoid.Died every frame.

Don’t do that. Please. It’s not unlikely that it’s another cause of your lag, and ironically will cause more memory leaks than it solves.

Hmm. what do you suggest I should do instead? Honestly, I don’t even know what that does. I got this script from another devForum and I modified and changed pretty much everything

Connect it outside the RunService connection.
Like so:

local HeartbeatConn = RunService.Heartbeat:Connect(function()
    -- ... code etc ...
end)

player.Character.Humanoid.Died:Once(function()
	Heartbeat:Disconnect() -- Stop memory leaks
end)

Really, you don’t need to do this. Removing that section of code works just as fine.

1 Like

I am not familiar with :disconnect. Does it basically stop the Heatbeat Loop?

Yes. The term “heartbeat loop” is a misnomer, it’s not a loop, it’s just a function that will run when the frame updates.

But Disconnect does essentially stop the event, yes. It also cleans it from memory so it doesn’t sit there and take up memory that could otherwise be used.

1 Like