How to move parts through script with almost zero lag!

Introduction.

Hello developers! When you first started, you probably imagined your games with tons of high-quality assets, visuals, sounds, and most importantly, a lag-less experience. You attempted to make your game happen, only to realize that there’s an issue. Your simple for i = 1, 10 do loop is causing tons of lag with your parts that you have in the game! You retest the game multiple times but get the same result. You check your FPS and realize that your biggest fear has come true. Your game is lagging. By now, your mind is flooded with questions like ‘How do I stop this from occurring?’, ‘What do I do?’, ‘Where do I even start?’. Then, you get the idea to go on the forum. However, another fear has been unlocked, and that is nobody knows how to help you solve your lag issue. This is not good. What do you do now? All hope is lost…

Or is it?

No. All hope is not lost just yet, because I am going to help you get through this trial. I’m going to go over the good and bad things regarding part movement via script. Without further ado, let’s dive into it!

The tutorial.

Welcome to the first category! Moving parts via script without lag may seem impossible, but I promise it’s really simple. Grab your headphones and listen to whatever tutorial music you think is best for this topic and hop along on this roller-coaster ride of part properties, and part movement methods. So, what comes first? Well…

What not to do.

It’s very easy to screw up when optimizing your game for the first time. So, in order to avoid those possibilities, let’s go over what you shouldn’t do first!

What Not to Do Number 1:

Trusting "anti-lag" scripts and plugins.

As a beginner, you might rely on the toolbox or the marketplace a bit more than you would like to. Because of this, you might trust anti-lag scripts and plugins that promise to “remove” lag in your games. None of these scripts and plugins work, as they either do not work, are viruses, attempt to change the maximum items in the “Debris” service, which is restricted, or “remove” viruses. You’re only wasting your time by adding these into your game, and they will not help your situation, and may even make it worse. I have been a victim of this before, so I know from experience!

What Not to Do Number 2:

Using multiple scripts for individual parts.

Let’s say you have a model with ten parts in it. You put a script in each individual part, and the code you have is:

while true do
     wait()
     script.Parent.Position = script.Parent.Position + Vector3.new(1, 0, 0)
end

You may be wondering, what’s the problem with this code? Why is it bad to clone this script and put it in every individual part? Here’s why.

  • Reason 1: It makes room for your worst enemy “Memory Leakage” to step in.

  • Reason 2: Because of this, memory leakage brings in your second worst enemy. Lag.

  • Reason 3: 10 while true do loops only worsen the leakage, and on top of that, forever, since loops never end unless broken.

Memory leakage is always bad and will always affect game performance. Never use multiple scripts for individual parts!

What Not to Do Number 3:

Not breaking loops or disconnecting functions when they are no longer needed.

You may think that using RunService or TweenService will improve your performance. It can if it’s used correctly. However, incorrect usage will not improve your performance, and may even make it worse. Take this for example:

local RunService = game:GetService("RunService")

RunService.Heartbeat:Connect(function(deltaTime)
	script.Parent.Position = script.Parent.Position + Vector3.new(1, 0, 0)
end)

What’s wrong here? This is such a simple script, why would this be bad? Remember memory leakage? If your function is not disconnected at some point, the script will constantly be firing the “Heartbeat” event by milliseconds, which is terrible, especially if you have been breaking rule number 2! Let’s look at this while true do loop next.

local Studs = 0

while true do
	wait()
	Studs = Studs + 1
	script.Parent.Position = script.Parent.Position + Vector3.new(1, 0, 0)
	if Studs >= 10 then
		break
	end
end

Notice my break in the while true do loop. I’m basically telling the script to stop the loop once the part has been moved 10 studs. This prevents the creep “Memory Leakage” from quite literally creeping in and wreaking havoc on the game’s performance. We’ll get more into this later, for now, let’s cover the next don’ts.

What Not to Do Number 4:

Leaving unnecessary properties on.

What’s an unnecessary property? Let’s go over to our properties tab and scroll down on one of our selected parts. Once we’ve reached “Collisions”, we’ll be able to see our available collision properties. We currently have “CanTouch” and “CanCollide”. However, if you turn “CanCollide” off, we’ve got one extra property. “CanQuery”. You may have never paid attention to these properties, but they seriously can impact performance! Let’s go over CanTouch briefly. What is it, and what does it do? Let’s look at this code:

local Part = script.Parent

Part.Touched:Connect(function(hit) -- Main focus, so don't worry if you don't understand the rest of the code below.
	if hit.Parent:FindFirstChild("Humanoid") then
		local Humanoid = hit.Parent:FindFirstChild("Humanoid")
		if Humanoid ~= nil then
			Humanoid:TakeDamage(15)
		end
	end
end)

“CanTouch” enables .Touched events. If you have a random rock lying around that’s just there for visuals, consider turning “CanTouch” off, which can improve performance by itself drastically. Now, what about our property “CanQuery”? Let’s look at yet another code example.

local Part = script.Parent
local Part2 = script.Parent.Part2

local PointA = Part.Position
local PointB = Part2.Position - Part.Position

local RayParams = RaycastParams.new() -- Main focus!
RayParams.FilterType = Enum.RaycastFilterType.Exclude
RayParams.FilterDescendantsInstances = {Part}

local Result = game.Workspace:Raycast(PointA, PointB, RayParams)
if Result then
	print("SUCCESS! The ray returned a result! Result: "..Result.Instance.Name)
end

“CanQuery” allows a part to be hit by a ray. If Part2’s “CanQuery” was turned off, the following message would not be printed and sent to the Output. (which is in the view tab) Since you most likely are not casting rays all of the time, you can most likely turn “CanQuery” off on most of your parts.

Of course, “CanCollide” detects if whether or not the part can be phased through or not. “CanCollide”, unlike the other properties, can freely be turned on or off without something else having to be tinkered with in the process. Turning lots of these properties off can be a major performance boost. “CastShadow” found under the “Appearance” tab on your selected part when turned off, can also improve performance. Turning off “CastShadow” on objects that are too small to cast a visible shadow or objects that don’t need a shadow in general will also be beneficial for your game.

Output Location:


(Ignore the terrible name of my test place :rofl:)

What Not to Do Number 5:

Using wait() instead of task.wait().

The global wait() is now deprecated and task.wait() is now what’s recommended, as it waits 1 frame, whereas wait() waits 2 frames. task.wait() is way better in terms of performance, even if it is just one less frame. task.wait() is highly accurate in comparison to wait(), which is less accurate and may not start your code at the desired time. Although that is unlikely, task.wait() will give you the desired results and is even what RunService.Heartbeat uses to calculate a “heartbeat”!

Superb! Now that we know what not to do regarding code and part properties, now we can get into what we should do, the moment we’ve all been waiting for.

What to do.

Rule Number 1:

Using one script for individual parts.

You may be wondering how you can do this. Well, wonder no more, because I am about to tell you what you should do! Take a look at this:

local RunService = game:GetService("RunService")

function Move(Part)
	RunService.Heartbeat:Connect(function(deltaTime)
		Part.Position = Part.Position + Vector3.new(1, 0, 0)
	end)
end

local Model = script.Parent

for _, v in ipairs(Model:GetChildren()) do
	if v:IsA("Part") then
		local Parts = v
		Move(Parts)
	end
end

Nice and simple, and it’s going to move all of the parts inside of our model! (or whatever you decide to use) Now we don’t need ten separate scripts that have the exact same purpose, and can you guess what that means? Great! Now we’ve just removed the key away from underneath the mat! Now to lock the door so that naughty memory leakage can’t creep in.

Rule Number 2:

Understanding what memory and memory leaks are.

Memory leaks are a pain and can bring the pest that we all know and hate. Lag. But, what is a memory leak? We’ve gone over how to avoid memory leakage, but what exactly is memory? Let me explain. Memory, from what I understand, saves temporary data. Of course, every data saver has its limits and can’t go beyond a certain amount of memory, before the memory eventually leaks. Take a faucet for example. If the pipes are clogged by too much trash, the water will not come out and will most likely leak trash along with the water, which is very dangerous. Sometimes, they can even explode because of being overfilled, and that’s a mess nobody is going to want to clean up.

Likewise, memory is basically like a faucet. If the memory is “clogged” by lots of events constantly being fired or at the same time, the server starts to overload because it cannot handle the number of events being fired, so it sends over way too much memory, which causes lag. Even a while true do loop that is never broken can overload the server and will cause lag to generate. So, always disconnect your events and always break loops that you no longer need. Just “leaving them alone” will eventually cause the number of events being fired or loops being run to build up, which, in turn, sends over too much memory, and creates our arch nemesis, lag. Here’s an example of how to disconnect events and break loops.

local RunService = game:GetService("RunService")

function Move(Part)
	local Connection = nil
	local Studs = 0

	Connection = RunService.Heartbeat:Connect(function(deltaTime)
		Studs += 1
		Part.Position = Part.Position + Vector3.new(1, 0, 0)
		if Studs >= 10 then
			Connection:Disconnect()
		end
	end)
end

local Model = script.Parent

for _, v in ipairs(Model:GetChildren()) do
	if v:IsA("Part") then
		local Parts = v
		Move(Parts)
	end
end

Now our script connection event will be disconnected! What about a loop, though?

local Model = script.Parent
local Studs = 0

while true do
	-- If you put local Model = script.Parent inside of the loop, you'd be adding unnecessary code that will be reassigned to the same thing every single time it loops, which we don't want if our goal is having high performance. 
	
	task.wait()
	for i, v in pairs(Model:GetChildren()) do
		if v:IsA("Part") then
			local Parts = v
			Parts.Position = Parts.Position+Vector3.new(1, 0, 0)
		end
	end
	
	Studs += 1
	
	if Studs >= 10 then
		break
	end
end

break will break our while true do loop and stop memory leakage. Great! We’ve locked our door! But wait, what about the windows? They’re still unlocked! Let’s go and lock them.

Rule Number 3:

Understanding the microprofiler.

Closed until further research is made!

Rule Number 4:

Using WeldConstraints.

WeldConstraint usage is very important, as welding multiple parts to a single part will be very performance boosting. It’s super simple and easy to do as well! You can either do it manually, or through script like this:

local RunService = game:GetService("RunService")
local Model = script.Parent
local MainPart = Model:WaitForChild("Part2", 30) -- Adding a timeout, which is the second parameter, can also prevent your script from infinitely waiting for an object to exist, which may prevent some strain on the server.

function AddWelds(Part: Part) -- This is called type checking. This basically makes a function require a certain thing before it can be called. In this case, the function is requiring a "Part" in order to be called. If AddWelds() is called without a parameter as seen in the for loop below, the script will error.
	local WeldConstraint = Instance.new("WeldConstraint") -- NEVER DO THIS: "Instance.new("WeldConstraint", Model)", as this is the WORST way to create a new instance! Always parent the new instance last!
	WeldConstraint.Parent = Part
	WeldConstraint.Part0 = Part
	WeldConstraint.Part1 = MainPart
	WeldConstraint.Parent = MainPart
	
	WeldConstraint.Enabled = true -- Just in case if the weld constraint is automatically disabled because of an unexpected delay in being generated.
end

function MovePart(Part) -- This is without type checking, which means the function can be called without a parameter. Because a part is needed for this specific function though, the script will still error.
	local Connection = nil
	local Studs = 0
	
	Connection = RunService.Heartbeat:Connect(function(deltaTime)
		Studs += 1
		Part.Position = Part.Position + Vector3.new(1, 0, 0)
		if Studs >= 10 then
			Connection:Disconnect()
		end
	end)
end

for i, v in ipairs(Model:GetChildren()) do
	if v:IsA("Part") then
		local Parts = v
		AddWelds(Parts) -- Adding the parameter so that the function can be called since I am using type checking. The : in the function "AddWelds" is known as type checking, which may be useful to you in the future.
		MovePart(MainPart)
	end
end

And boom! There we have it! Our ten parts have been automatically welded to one part through script! Now, we have a better chance of our performance not taking a nose-dive once the script is enabled. We’ve locked our windows now! Our game is now secure from memory leakage coming from our scripts! Thank you for taking the time to read all of this if you did, as this took a very long time to make. If you know someone that may have a problem with maintaining performance when moving parts via script, you can share this topic with them!

Thank you so much for reading! Please let me know if you found any error in my code or any false information that I may have given. If you have any suggestions, please let me know! Goodbye! :smiley:

(Check out my topic regarding accurate timing when moving your parts!)

Topic link

Accurate timing when moving parts through script for beginners

Rate my topic? :pleading_face:

Rate the tutorial!
  • It was helpful!
  • It wasn’t very helpful…
  • I don’t understand it.
  • I’d rather not say.

0 voters

19 Likes

Jeez I feel kinda dumb because I legit do all of the bad things listed lol.

A script in everything. A bunch of memory leaks in my scripts. And having CanCollide for unnecessary stuff.

For the memory leaks couldn’t I just use gcinfo tho (obviously after removing strong references)?

2 Likes

No worries! I understand, as I also didn’t know any of this back when I first started. As for gcinfo, I suppose you can? I’m not exactly familiar with it, but I’ll try to do some research on it. I believe using print(gcinfo)) is going to be the most that you can do with gcinfo, but I could very well be wrong.

1 Like

I’m pretty sure gcinfo is short for “garbage collect information”.

I’m pretty sure this is the replacement for collectgarbage which I think is deprecated now.

Returns the total memory heap size in kilobytes. The number reflects the current heap consumption from the operating system perspective, which fluctuates over time as garbage collector frees objects.

That is an exact copy and paste of the information I got from the DevForum. Still not sure of how gcinfo could be used though.

1 Like

Is task.wait() meant to be in the what not to do area, or the what to do area? I’m a bit confused.

Sorry about that confusion. I was saying that task.wait() should be used instead of wait(). task.wait() is more efficient than wait(). Just edited the post. I hope this clears up any confusion.

Thank you, this quells some worries I had.

1 Like