Loading...
Loading...
Use when optimizing a Roblox game for better frame rates, reducing lag, improving server or client performance, diagnosing FPS drops, handling large worlds, or when asked about streaming, draw calls, object pooling, LOD, MicroProfiler, or expensive loop operations.
npx skill4agent add sentinelcore/roblox-skills roblox-performance| Technique | Impact | When to Use |
|---|---|---|
| StreamingEnabled | High | Large open worlds |
| Object pooling | High | Frequent spawn/destroy |
| Cache references outside loops | High | Heartbeat/RenderStepped |
| Medium | All scripts |
| MeshParts over Unions | Medium | Many unique shapes |
| LOD (hide at distance) | Medium | Complex models |
| Anchor static parts | Medium | Reduce physics budget |
| Limit PointLights | High | Any scene with many lights |
-- Studio: Workspace > StreamingEnabled = true
workspace.StreamingEnabled = true
workspace.StreamingMinRadius = 64
workspace.StreamingTargetRadius = 128nilif part thenModel.LevelOfDetail = Disabledworkspace:RequestStreamAroundAsync(targetPosition, 5) -- 5s timeoutRunService.HeartbeatRenderSteppedRunService.Heartbeat:Connect(function()
local char = workspace:FindFirstChild(player.Name)
local humanoid = char and char:FindFirstChild("Humanoid")
if humanoid then humanoid.WalkSpeed = 16 end
end)local humanoid = nil
Players.LocalPlayer.CharacterAdded:Connect(function(char)
humanoid = char:WaitForChild("Humanoid")
end)
RunService.Heartbeat:Connect(function(dt)
if not humanoid then return end
humanoid.WalkSpeed = 16 -- cached reference, no search
end)game:GetService()FindFirstChildGetChildrenGetDescendantslocal TICK_INTERVAL = 0.5
local elapsed = 0
RunService.Heartbeat:Connect(function(dt)
elapsed += dt
if elapsed < TICK_INTERVAL then return end
elapsed = 0
-- expensive work here, runs 2×/sec instead of 60×/sec
end)taskwait()spawn()| Legacy | Modern |
|---|---|
| |
| |
| |
| |
-- ObjectPool ModuleScript
local ObjectPool = {}
ObjectPool.__index = ObjectPool
function ObjectPool.new(template, initialSize)
local self = setmetatable({ _template = template, _available = {} }, ObjectPool)
for i = 1, initialSize do
local obj = template:Clone()
obj.Parent = nil
table.insert(self._available, obj)
end
return self
end
function ObjectPool:Get(parent)
local obj = table.remove(self._available) or self._template:Clone()
obj.Parent = parent
return obj
end
function ObjectPool:Return(obj)
obj.Parent = nil
table.insert(self._available, obj)
end
return ObjectPool-- Usage
local pool = ObjectPool.new(ReplicatedStorage.Bullet, 20)
local function fireBullet(origin)
local bullet = pool:Get(workspace)
bullet.CFrame = CFrame.new(origin)
task.delay(3, function() pool:Return(bullet) end)
endModel.LevelOfDetail = Automatic-- LocalScript
local INTERVAL = 0.2
local LOD_DISTANCE = 150
local elapsed = 0
RunService.Heartbeat:Connect(function(dt)
elapsed += dt
if elapsed < INTERVAL then return end
elapsed = 0
local dist = (workspace.CurrentCamera.CFrame.Position - model.PrimaryPart.Position).Magnitude
local visible = dist < LOD_DISTANCE
for _, v in model:GetDescendants() do
if v:IsA("BasePart") then
v.LocalTransparencyModifier = visible and 0 or 1
end
end
end).fbxSmoothPlasticTextureIdCtrl+F6Ctrl+PheartbeatSignalphysicsSteppedrenderRunService.Heartbeat:Connect(function()
debug.profilebegin("MySystem")
-- your code
debug.profileend()
end)| Cause | Fix |
|---|---|
| Thousands of individual parts | Merge into MeshParts |
| Unanchored static geometry | |
Many | Limit to ~10–20 dynamic lights per area |
| High-rate ParticleEmitters | Lower |
| Replace with |
| Cache on load |
| StreamingEnabled off on large maps | Enable it |
| Use |
| Mistake | Fix |
|---|---|
| Cache reference on character/model load |
| Destroying and re-creating bullets/effects | Use an object pool |
| |
| All parts with unique materials | Standardize to a small set of shared materials |
| ParticleEmitters enabled off-screen | Disable |
| Physics on decorative parts | |