Color changing parts causing massive performance issues

In my game there are mounts that can be shiny. When a mount is shiny some parts on it will smoothly transition through a rainbow color though for some reason this is causing massive performance issues for clients. If there are more than 5 shiny mounts in the world at one time the game becomes almost unplayable with fps sometimes dropping below 30.

I find this very strange because the mounts only have about 15 parts on them that change color which means that only about 65 color changing parts make the game completely unplayable for everybody. I tried the same code on a fresh baseplate with hundreds of color changing parts at the same time and there was virtually no performance impact. How is that possible?

I’ve ironed out all issues such as memory leaks and instances hanging around in workspace. I’ve done this before in previous games and have had no issues but am all of a sudden having major issues in this game. It’s not a big game by any means and fps only starts to drop as soon as a shiny mount is taken out.

Is there any way I can optimize this code to stop the lag? Or will I have to look for another way to do this?

function MountHandlerServer:_AddShine(MountModel)
    local PartsToColor = {}
    for _, Part in ipairs(MountModel:GetDescendants()) do
        if Part:IsA("BasePart") and Part:GetAttribute("ColorCanModify") == true then
            Part.Color = Color3.fromRGB(255, 255, 0)
            Part.Material = Enum.Material.Neon
            table.insert(PartsToColor, Part)
        end
    end
    

    local HeartbeatConnection
    HeartbeatConnection = RunService.Heartbeat:Connect(function(dt)
        local Hue = os.clock() % 5 / 5 --> Will take 5 seconds to go through entire rainbow
        local Color = Color3.fromHSV(Hue, 0.6, 1)
        for _, Part in ipairs(PartsToColor) do
            Part.Color = Color
        end
        if MountModel.Parent == nil then
            HeartbeatConnection:Disconnect()
            HeartbeatConnection = nil
        end
    end)
end

I don’t really know whether or not this will solve it but you are creating a new Heartbeat connection every time a new mount model is created. What if you just have one connection iterate through all of the mount models and update the parts’ colours? This would cut down on function calls by a lot depending on how many mounts are in the server. I’d assume it would also handle cleanup as instances are destroyed. It would also help in the future if you wanted to easily add more parts to the shiny tag without having to go through this script.

For example,

local collectionService = game:GetService('CollectionService')
local runService = game:GetService('RunService')

runService.Heartbeat:Connect(function(dt)
    local colour = Color3.fromHSV(os.clock() % 5 / 5, 0.6, 1)
    for _, part in collectionService:GetTagged('Shiny') do
        part.Color = colour
    end
end)

-- now to add the parts to the shiny tag
function mountHandler:AddShine(model)
    for _, part in model:GetDescendants() do
        if not part:IsA('BasePart') then
            continue
        end
        collectionService:AddTag(part, 'Shiny')
    end
end)

Tried it and unfortunately the exact same behaviour happens. I just did a test where I call the :_AddShine function on a model with 1024 parts in the same game and somehow there was absolutely no performance impact whatsoever. The parts on the mounts are animated and attached to the player so maybe that has something to do with the lag?

I just found a post that’s very similar to what’s happening to me although our code is nothing alike and I can’t find any similarities that would lead to this lag.

Just to confirm something, are you doing this on the client?

If you’re not doing this on the client, that’s probably the issue.

No, I’m doing this on the server. But if I moved it to the client how would this fix the issue? The lag is occurring on the client, not the server. And as I said earlier, I tested the same function with 1024 parts and there was no performance hit whatsoever and that was called on the server.

I used to have a game with 600+ color changing bricks and the previous owner had followed the same method that you have.

I’ve done optimization on their script, converted it all to localscript, and used TweenService every half second to change the color. You might find this useful if you only need to periodically change the color, as it will not run the function every heartbeat.

There’s many ways to tackle this issue, but so far this is the best one that I’ve found because the game was able to handle 2000+ bricks after optimization.

I could be entirely wrong as I’m not entirely too familiar but from my personal experience, the server, if given enough stress, tends to actually impact the client more than if it was entirely on the client.

By offsetting the stress from the server to the client, the client handles the heavy computing instead of the server. The server has to do more work as it also has to replicate these functions across multiple of players.

Thereby, simply moving effects related events to the client, it will help the performance of the players.

That’s why I have a general rule of doing:
Effects on client
Important stuff on server.

Moved everything to the client, same issue. The only difference is now the Roblox client accurately displays the FPS and shows it dipping.


With just 2 mounts in the world at one time and ~10 parts changing color I’m getting 45 fps.

1 Like