I’m creating a script that checks the movement of unanchored parts and tracks the velocity. It’s a simple code and it works fine for me.
In-depth explanation of the code
Basically the whole code runs on heartbeat, it checks for every part inside a folder and checks if it’s unanchored and a BasePart, later it checks if the BasePart was inserted inside a table containing all the parts being tracked, if it wasn’t, insert it, otherwise, update it. After all of that later it will check for
unnecessary values inside the table, if the BasePart inside the table does not exist, the parent does
not exist, or it isn’t moving, if any of those conditions is met, the value will be removed.
The code
local RunService = game:GetService("RunService")
local Heartbeat = RunService.Heartbeat
local UserGeneratedObjects = game.Workspace:FindFirstChild("UserGeneratedBlocks")
local VelocityTracker = {}
Heartbeat:Connect(function(Step)
--// Tracking objects
for _, Object in pairs(UserGeneratedObjects:GetDescendants()) do
if Object:IsA("BasePart") then
print("It's a base part")
if not Object.Anchored then
print("Not anchored")
if not VelocityTracker[Object] then
print("Value not found")
VelocityTracker[Object] = Object.AssemblyLinearVelocity
else
if Object.AssemblyLinearVelocity == Vector3.new(0,0,0) then
print("Object is not moving, removing value")
VelocityTracker[Object] = nil
continue
end
print("Value found")
local OldVelocity = VelocityTracker[Object]
VelocityTracker[Object] = Object.AssemblyLinearVelocity
print((OldVelocity - VelocityTracker[Object]).Magnitude)
if (OldVelocity - VelocityTracker[Object]).Magnitude >= 190 then
print("Velocity magnitude over required amount")
end
end
end
end
end
--// Checking for unnecessary values
for TrackedObject, TrackedVelocity in pairs(VelocityTracker) do
if not TrackedObject then
VelocityTracker[TrackedObject] = nil
elseif TrackedObject then
if not TrackedObject.Parent or TrackedObject.Anchored then
VelocityTracker[TrackedObject] = nil
end
end
end
end)
The issue I’m having currently is that the Script Activity goes around 6-20% when the code is tracking around 1000 parts:
An idea I had would be tracking objects every 0.2 or more seconds depending on the server’s performance, basically skipping some frames on Heartbeat, but that didn’t work. Again, the code is running fine, but the only issue I currently have is the Script Activity of it.
I would like to know if there’s any better way to optimize this script. Any kind of help is appreciated.
for TrackedObject, TrackedVelocity in pairs(VelocityTracker) do
if not TrackedObject or (not TrackedObject.Parent or TrackedObject.Anchored) then
VelocityTracker[TrackedObject] = nil
end
end
Wouldn’t it be more effective if, instead of checking every step, you only checked when the Velocity property changed? Like with GetPropertyChangedSignal()?
I’m not sure about that, I remember reading a few topics around a year ago pointing out that using too many connections might cause issues related to performance, in this case I’m not sure, correct me if I’m wrong.
Mind explaining me what do you mean by no yielding?
Basically the player can build stuff with blocks, like a Build to Survive game or Creeper Chaos, after the building time passes, this script gets enabled, all parts are unanchored and welded, this script is used to detect when a block is for example falling off a building or anything like that, for example: a block made of glass that breaks when there’s a sudden change of speed from X to 0, or a reactive block which does a special function after X velocity.
They are created by the server with a RemoteEvent during the building time, and the parts inside are blocks created by the player.
What I did was instead of looping through all parts every heartbeat, I’d manually add them to a registry of sorts and loop through that. So every frame it would only check unanchored BaseParts instead of having to check everything inside the folder, like the models.
I also added optimizations like if there were no parts to check, it would stop running and then it would restart running if new parts were added. Should save you a very small amount of script activity.
New Code
local WorkspaceService = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local Connections = {}
local Registry = {}
local function Unregister(Object)
if Registry[Object] ~= nil then
--# Sets the registry of the
--| object to nil as it's no
--| longer needed.
Registry[Object] = nil
if #Registry == 0 then
--# Disconnects most connections because
--| they're no longer needed.
for Index, Connection in pairs(Connections) do
if Index ~= "ObjectAdded" and Connection.Connected == true then
Connection:Disconnect()
end
end
end
end
end
local function Register(Object)
if Object:IsA("BasePart") and Object.Anchored == false and Registry[Object] == nil then
--# After an object passes the requirements,
--| it will get registered.
Registry[Object] = Vector3.new(0,0,0)
if Connections["ObjectRemoved"] == nil then
Connections["ObjectRemoved"] = WorkspaceService:WaitForChild("UserGeneratedBlocks").DescendantRemoving:Connect(Unregister)
end
if Connections["Heartbeat"] == nil then
--# Creates the track function to track
--| all registered parts.
Connections["Heartbeat"] = RunService.Heartbeat:Connect(function()
for Object, Velocity in pairs(Registry) do
if Object.AssemblyLinearVelocity == Vector3.new(0,0,0) then
Unregister(Object)
else
Registry[Object] = Object.AssemblyLinearVelocity
end
end
end)
end
end
end
--# Creates the initial connection needed
--| to register new parts.
Connections["ObjectAdded"] = WorkspaceService:WaitForChild("UserGeneratedBlocks").DescendantAdded:Connect(Register)
--# Loops through all objects that may
--| have been missed or already added
--| before the script started.
for _, Object in pairs(WorkspaceService:WaitForChild("UserGeneratedBlocks"):GetDescendants()) do
Register(Object)
end
Let me know if something in here doesn’t work and I’ll try to modify it to fit your game.
Extra Note:
Also when no parts are moving anymore, the script will stop until more parts are added. This means that script activity will drop to zero with the new code I gave you when all parts have stopped moving.
As long as you destroy the connections when the parts are gone, it should not lag. Checking the velocity only when it changes allows your computer to save memory when parts are stationary.
I would make something like this:
local Connections = {}
local VelocityTracker = {}
Folder.ChildAdded:Connect(function(child)
if child:IsA("BasePart") then
Connections[child] = child:GetPropertyChangedSignal("Velocity"):Connect(function()
VelocityTracker[child] = child.Velocity
)
end
)
Folder.ChildRemoved:Connect(function(child)
if Connections[child] then
Connections[child]:Disconnect()
Connections[child] = nil
VelocityTracker[child] = nil
end
)
It works fine, thanks for helping, but I have an issue, as the code is intended to work, when a part is not moving, it gets removed from the registry table, but when it starts to move again, it does not get added to the table again, I can fix it for myself by just keeping track of the parts and not removing them from the table, but I would like to know if you have a better idea.
When an object is destroyed, all subsequent event connections on the object are automatically disconnected.
The only case where this is necessary is if you’re connecting up another connection, unrelated to the object itself.