关于 roblox-performance
面向 Roblox 开发者的性能优化指南,介绍世界流送、逐帧循环优化、对象复用,以及渲染和物理开销控制,并说明如何使用 MicroProfiler 分析。
主要功能
- 01大型世界的 StreamingEnabled
- 02缓存并节流逐帧循环
- 03对象池与 LOD
- 04降低绘制调用和物理开销
- 05使用 MicroProfiler 检查
适用场景
- 01优化大型 Roblox 地图
- 02减少对象的反复创建
- 03排查帧率瓶颈
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sentinelcore/ roblox-skills --skill roblox-performance安装后在 Claude Code 中输入/roblox-performance确认已加载。
详细介绍
Roblox 性能优化
快速参考
| 技术 | 影响 | 适用场景 |
|---|---|---|
| StreamingEnabled | 高 | 大型开放世界 |
| 对象池 | 高 | 频繁生成/销毁 |
| 在循环外缓存引用 | 高 | Heartbeat/RenderStepped |
使用 task.wait() 而非 wait() | 中 | 所有脚本 |
| 使用 MeshPart 而非 Union | 中 | 需要许多独特形状时 |
| LOD(远距离隐藏) | 中 | 复杂模型 |
| 锚定静态部件 | 中 | 减少物理预算 |
| 限制 PointLight 数量 | 高 | 有许多光源的场景 |
StreamingEnabled
大型世界请启用此功能——引擎只会向客户端发送附近的部件。
-- Studio: Workspace > StreamingEnabled = true
workspace.StreamingEnabled = true
workspace.StreamingMinRadius = 64
workspace.StreamingTargetRadius = 128
- 半径范围外的部件在客户端上为
nil——务必使用if part then进行检查。 - 对必须始终存在的模型,将
Model.LevelOfDetail = Disabled。 - 在过场动画或传送前预先流式加载区域:
workspace:RequestStreamAroundAsync(targetPosition, 5) -- 5s timeout
热路径循环优化
RunService.Heartbeat 和 RenderStepped 每帧都会触发(约每秒 60 次)。请尽量精简其中的操作。
不佳——每帧都搜索层级结构
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)
良好——只缓存一次引用,仅在需要时执行操作
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()和部件引用。 - 切勿在 Heartbeat 中调用
FindFirstChild、GetChildren或GetDescendants。 - 对不需要每帧执行的操作进行限频:
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)
task 库与旧版调度器
始终使用 task——wait() 和 spawn() 在高负载下会受到限频,且已弃用。
| 旧版 | 现代版 |
|---|---|
wait(n) | task.wait(n) |
spawn(fn) | task.spawn(fn) |
delay(n, fn) | task.delay(n, fn) |
coroutine.wrap(fn)() | task.spawn(fn) |
对象池
重复使用实例,而不是每帧都创建并销毁它们。
-- 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)
end
细节层次(LOD)
内置: 设置 Model.LevelOfDetail = Automatic——引擎会自动将远处的部件合并为替代网格。
手动按距离调整 LOD:
-- 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)
减少绘制调用
- 将共享材质的部件合并为一个 MeshPart(从 Blender 导出为
.fbx)。 - MeshPart 比 CSG Union 更适合批处理(Union 会在运行时重新三角化)。
- 重复使用材质——10 个共享
SmoothPlastic的部件,比 10 种独特纹理的开销小得多。 - 在单个 MeshPart 上使用
TextureId,而不是在许多部件上叠加 Decal。
使用 MicroProfiler 进行性能分析
- 在游戏中按
Ctrl+F6打开 MicroProfiler。 - 按
Ctrl+P暂停并检查单帧。 - 留意
heartbeatSignal(Lua)、physicsStepped(物理)或render(GPU)中的宽条。 - 为自己的代码添加标签:
RunService.Heartbeat:Connect(function()
debug.profilebegin("MySystem")
-- your code
debug.profileend()
end)
常见的 FPS 杀手
| 原因 | 修复方法 |
|---|---|
| 数千个独立部件 | 合并为 MeshPart |
| 未锚定的静态几何体 | 对任何不会移动的对象设置 Anchored = true |
许多 PointLight/SpotLight 实例 | 将每个区域的动态光源限制在约 10–20 个 |
| 高频率 ParticleEmitter | 降低 Rate 和 Lifetime;移出屏幕时禁用 |
高负载下使用 wait() | 替换为 task.wait() |
在 Heartbeat 中使用 FindFirstChild 链式调用 | 加载时缓存 |
| 大型地图未启用 StreamingEnabled | 启用它 |
到处都设置 Model.LevelOfDetail = Disabled | 在安全情况下使用 Automatic |
常见错误
| 错误 | 修复方法 |
|---|---|
每帧调用 workspace:FindFirstChild | 加载角色/模型时缓存引用 |
| 销毁并重新创建子弹/特效 | 使用对象池 |
在紧密循环中使用 wait() | 使用 task.wait() |
| 所有部件都使用独特材质 | 统一使用少量共享材质 |
| ParticleEmitter 在屏幕外仍处于启用状态 | 粒子源不可见时将 Enabled 设为禁用 |
| 装饰性部件启用了物理效果 | 设置 Anchored = true |
评论
支持 Markdown