roblox-performance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRoblox Performance Optimization
Roblox 性能优化
Quick Reference
快速参考
| 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 |
| 优化技术 | 影响程度 | 适用场景 |
|---|---|---|
| StreamingEnabled | 高 | 大型开放世界 |
| 对象池 | 高 | 频繁生成/销毁对象的场景 |
| 在循环外缓存引用 | 高 | 使用Heartbeat/RenderStepped的场景 |
使用 | 中 | 所有脚本 |
| 使用MeshPart替代Union | 中 | 存在大量独特形状的场景 |
| LOD(远距离隐藏) | 中 | 复杂模型 |
| 锚定静态部件 | 中 | 减少物理计算开销 |
| 限制PointLight数量 | 高 | 存在大量光源的场景 |
StreamingEnabled
StreamingEnabled
Enable for large worlds — engine sends only nearby parts to the client.
lua
-- Studio: Workspace > StreamingEnabled = true
workspace.StreamingEnabled = true
workspace.StreamingMinRadius = 64
workspace.StreamingTargetRadius = 128- Parts outside the radius are on the client — always guard with
nil.if part then - Set on models that must always be present.
Model.LevelOfDetail = Disabled - Pre-stream an area before a cutscene or teleport:
lua
workspace:RequestStreamAroundAsync(targetPosition, 5) -- 5s timeout在大型开放世界中启用——引擎只会将客户端附近的部件发送给客户端。
lua
-- Studio: Workspace > StreamingEnabled = true
workspace.StreamingEnabled = true
workspace.StreamingMinRadius = 64
workspace.StreamingTargetRadius = 128- 超出半径的部件在客户端上为——务必用
nil进行判断。if part then - 对于必须始终显示的模型,设置。
Model.LevelOfDetail = Disabled - 在过场动画或传送前预加载指定区域:
lua
workspace:RequestStreamAroundAsync(targetPosition, 5) -- 5秒超时Hot-Path Loop Optimization
热路径循环优化
RunService.HeartbeatRenderSteppedRunService.HeartbeatRenderSteppedBad — searching the hierarchy every frame
不良示例——每帧都搜索层级结构
lua
RunService.Heartbeat:Connect(function()
local char = workspace:FindFirstChild(player.Name)
local humanoid = char and char:FindFirstChild("Humanoid")
if humanoid then humanoid.WalkSpeed = 16 end
end)lua
RunService.Heartbeat:Connect(function()
local char = workspace:FindFirstChild(player.Name)
local humanoid = char and char:FindFirstChild("Humanoid")
if humanoid then humanoid.WalkSpeed = 16 end
end)Good — cache references once, do work only when needed
良好示例——仅缓存一次引用,仅在需要时执行操作
lua
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)Rules:
- Cache and part references outside the loop.
game:GetService() - Never call ,
FindFirstChild, orGetChildreninside Heartbeat.GetDescendants - Throttle work that doesn't need every frame:
lua
local 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)lua
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 -- 使用缓存的引用,无需搜索
end)规则:
- 在循环外缓存和部件引用。
game:GetService() - 绝不在Heartbeat中调用、
FindFirstChild或GetChildren。GetDescendants - 对无需每帧执行的操作进行节流:
lua
local TICK_INTERVAL = 0.5
local elapsed = 0
RunService.Heartbeat:Connect(function(dt)
elapsed += dt
if elapsed < TICK_INTERVAL then return end
elapsed = 0
-- 此处执行高开销操作,每秒仅运行2次而非60次
end)task Library vs Legacy Scheduler
task库 vs 旧版调度器
Always use — and throttle under load and are deprecated.
taskwait()spawn()| Legacy | Modern |
|---|---|
| |
| |
| |
| |
始终使用——和在高负载下会被节流,且已被弃用。
taskwait()spawn()| 旧版API | 新版API |
|---|---|
| |
| |
| |
| |
Object Pooling
对象池
Reuse instances instead of creating and destroying them every frame.
lua
-- 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 ObjectPoollua
-- 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)
end复用实例而非每帧创建和销毁对象。
lua
-- 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 ObjectPoollua
-- 使用示例
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)
endLevel of Detail (LOD)
细节层级(LOD)
Built-in: Set — engine merges distant parts into an imposter mesh automatically.
Model.LevelOfDetail = AutomaticManual distance-based LOD:
lua
-- 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)内置LOD: 设置——引擎会自动将远处的部件合并为 impostor 网格。
Model.LevelOfDetail = Automatic基于距离的手动LOD:
lua
-- 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)Reducing Draw Calls
减少绘制调用
- Merge parts that share a material into one MeshPart (export from Blender as ).
.fbx - MeshParts batch better than CSG Unions (Unions re-triangulate at runtime).
- Reuse materials — 10 parts sharing costs far less than 10 unique textures.
SmoothPlastic - Use on a single MeshPart instead of stacking Decals on many parts.
TextureId
- 将使用相同材质的部件合并为单个MeshPart(从Blender导出为格式)。
.fbx - MeshPart的批处理性能远优于CSG Union(Union会在运行时重新三角化)。
- 复用材质——10个使用的部件开销远低于10个使用独特纹理的部件。
SmoothPlastic - 在单个MeshPart上使用,而非在多个部件上叠加Decal。
TextureId
Profiling with MicroProfiler
使用MicroProfiler进行性能分析
- Press in-game to open MicroProfiler.
Ctrl+F6 - Press to pause and inspect a single frame.
Ctrl+P - Look for wide bars in (Lua),
heartbeatSignal(physics), orphysicsStepped(GPU).render - Label your own code:
lua
RunService.Heartbeat:Connect(function()
debug.profilebegin("MySystem")
-- your code
debug.profileend()
end)- 在游戏中按打开MicroProfiler。
Ctrl+F6 - 按暂停并检查单帧情况。
Ctrl+P - 查看(Lua逻辑)、
heartbeatSignal(物理计算)或physicsStepped(GPU)中的宽条形块(代表高开销)。render - 为自己的代码添加标签:
lua
RunService.Heartbeat:Connect(function()
debug.profilebegin("MySystem")
-- 你的代码
debug.profileend()
end)Common FPS Killers
常见FPS杀手
| 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 |
| 原因 | 修复方案 |
|---|---|
| 数千个独立部件 | 合并为MeshPart |
| 未锚定的静态几何体 | 对任何不会移动的部件设置 |
大量 | 每个区域限制在10-20个动态光源以内 |
| 高频率的ParticleEmitters | 降低 |
高负载下使用 | 替换为 |
在Heartbeat中链式调用 | 在加载时缓存引用 |
| 大型地图未启用StreamingEnabled | 启用该功能 |
所有模型都设置 | 在安全的场景下使用 |
Common Mistakes
常见错误
| 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 | |
| 错误做法 | 修复方案 |
|---|---|
每帧调用 | 在角色/模型加载时缓存引用 |
| 销毁并重新创建子弹/特效 | 使用对象池 |
在紧凑循环中使用 | 替换为 |
| 所有部件都使用独特材质 | 标准化为少量共享材质 |
| 粒子源不可见时仍启用ParticleEmitters | 当粒子源不可见时禁用 |
| 装饰性部件启用物理效果 | 设置 |