roblox-performance について
Roblox 開発者向けのパフォーマンス最適化ガイドです。ワールドストリーミング、フレームループの効率化、オブジェクト再利用、描画・物理負荷の調整と MicroProfiler による分析を扱います。
主な機能
- 01大規模ワールド向け StreamingEnabled
- 02フレームループのキャッシュと間引き
- 03オブジェクトプールと LOD
- 04描画呼び出しと物理負荷の削減
- 05MicroProfiler による調査
活用シーン
- 01大規模 Roblox マップの最適化
- 02オブジェクトの反復生成の削減
- 03フレームレート低下の原因調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ sentinelcore/ roblox-skills --skill roblox-performanceインストール後、Claude Code で/roblox-performanceを入力して読み込まれたか確認します。
詳細
Roblox パフォーマンス最適化
クイックリファレンス
| テクニック | 効果 | 使用すべき場面 |
|---|---|---|
| StreamingEnabled | 高 | 大規模なオープンワールド |
| オブジェクトプーリング | 高 | 頻繁な生成・破棄 |
| ループ外での参照キャッシュ | 高 | Heartbeat/RenderStepped |
wait() ではなく task.wait() | 中 | すべてのスクリプト |
| Union ではなく MeshPart | 中 | 多数のユニークな形状 |
| 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 ライブラリ vs レガシースケジューラ
常に 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)
ドローコールの削減
- 同じマテリアルを共有するパーツを 1 つの MeshPart に統合します(Blender から
.fbxとしてエクスポート)。 - MeshPart は CSG Union よりも効率的にバッチ処理されます(Union は実行時に再トライアングル化されます)。
- マテリアルを再利用します —
SmoothPlasticを共有する 10 個のパーツは、10 個のユニークなテクスチャよりもはるかに低コストです。 - 多数のパーツに Decal を重ねるのではなく、単一の MeshPart で
TextureIdを使用します。
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 対応