このガイドでは、ゲーム内のインスタンスストリーミングを効率的かつ効果的に使用するためのいくつかのテクニックを概説します。「すべてに適した」ストリーミングゲームの設計に関する解決策はありませんが、これらの高レベルのステップに従うことで、ほとんどの部分をカバーできます。
ストリーミングプロパティ
StreamingEnabledがスタジオ内のWorkspaceオブジェクトに対してオンにされると、関連するプロパティを以下の推奨値に設定します。
| プロパティ | 推奨値 |
|---|---|
| EnableSLIMAvatars | 適切な場合、標準のリグアバターを軽量でアニメーションされたスタンドインとしてレンダリングするためにEnabledを使用します。詳細についてはSLIMアバターを参照してください。 |
| ModelStreamingBehavior | ModelsのBasePart子孫に対して最も効率的なストリーミングを有効にするためにImprovedを使用します。 |
| StreamingIntegrityMode | 不必要にまたは頻繁に一時停止することなく、ゲームプレイの整合性をバランスさせるためにPauseOutsideLoadedAreaを使用します。 |
| StreamingMinRadius | エンジンが低スペックデバイス向けにゲームを最大限にスケールダウンできるように、デフォルトの64を使用します。 |
| StreamingTargetRadius | 高スペックデバイスのプレイヤーに対する視認性と合理的なメモリフットプリントのバランスを取るために、デフォルトの1024を使用します。 |
| StreamOutBehavior | クライアントがコンテンツを積極的にガーベジコレクトできるようにするためにOpportunisticを使用し、メモリ使用量を大幅に削減し、メモリ不足によるクラッシュを防ぎます。 |
モデルの詳細レベル
Model.LevelOfDetailは、軽量の合成メッシュまたはインポスターで非ストリーミングのModelコンテンツを補完し、世界を視覚的に完全に見せるのに役立ちます。SLIM(スケーラブル軽量インタラクティブモデル)は特に効果的で、プレイヤーはSLIMメッシュと完全にストリーミングされたオリジナルを区別できないことがよくあります。
最良の結果を得るために:
- 空間的および論理的に関連するパーツをグループ化します。たとえば、車のすべてのパーツをまとめます。
- 各モデルの空間的範囲を約64立方スタッド未満に保ち、実際のモデル全体が一緒にストリーミングされる可能性を高めます。モデルの範囲が非常に大きい場合は、より小さなモジュラーモデルに分解し、それぞれに適切なLevelOfDetailを適用します。
モデル構造
モデルの詳細レベルを設定することに加えて、Modelsの構造と設定は、ストリーミングのパフォーマンスに大きな影響を与えます。既存のゲームを構築または変換する際には:
論理グループ化のための原子モデルを使用する — スクリプトがモデル内のすべてのパーツにアクセスする必要がある場合は、そのModelStreamingModeをAtomicに設定します。これにより、クライアント側のスクリプトは、過度にWaitForChild()を使用することなく、モデル内のインスタンスに安全にアクセスできます(ただし、そのようなスクリプトは、全体の原子モデルに対してWaitForChild()を使用する必要があります)。
永続モデルを最小限に抑える — 永続モデルは、参加後に読み込まれ、決してストリーミングアウトせず、メモリを永続的に占有します。モデルのModelStreamingModeをPersistentに設定するのは、常にスクリプトにアクセス可能である必要がある場合のみです。
SLIMアバター
現在ストリーミングされていないエリアのプラットフォームアバターはデフォルトでは表示されませんが、Workspace.EnableSLIMAvatarsを有効にすると、適切な場合に標準リグアバターが軽量でアニメーションされたスタンドインとしてレンダリングされます。実際には、エンジンは次のように動作します:
- 実際のアバターモデルがストリーミングアウトすると、SLIMバージョンをレンダリングします。
- 利用可能なリソースに基づいて、ストリーミング半径内でもSLIMとフル解像度の表現を切り替えます。
- シーンの重要性と利用可能な帯域幅に基づいてSLIMアニメーションを制限します。
SLIMアバターは、体、頭、レイヤー付き衣服、およびアクセサリーを持つR15標準リグプレイヤーキャラクターをサポートします。R6アバター、NPC、およびカスタムプロポーションのアバターは除外されます。サポートされているアバター構成、除外されているアバター構成、パフォーマンスデータ、およびトラブルシューティングのヒントの完全なリストについては、SLIMアバターを参照してください。
スクリプトパターン
次のスクリプトパターンは、ストリーミングの影響を最も受けるものです。正しい戦略はコードの意図によって異なるため、各パターンには適切な場合に複数のオプションがリストされています。
子孫への直接インデックス
Workspaceの子孫に.演算子でインデックスを付けると、パス内のインスタンスが現在ストリーミングされていない場合、エラーが発生します。これは、FindFirstChild()、FindFirstChildWhichIsA()、およびFindFirstChildOfClass()にも当てはまり、子がストリーミングされていない場合はnilを返します。
local house1 = workspace:FindFirstChild("House1") -- "House1"がストリーミングされていない場合はnil
local door = workspace.House1.Door -- "House1"または"Door"がストリーミングされていない場合は壊れる同様のパターンは、Player.CharacterAdded接続内でHumanoidや他のキャラクターの子孫に直接アクセスすることです。ストリーミング下では、キャラクターモデルはすべての子孫が複製される前にWorkspaceに親付けされるため、直接インデックスが失敗します。
local Players = game:GetService("Players")
local player = Players.LocalPlayer
player.CharacterAdded:Connect(function(character)
local humanoid = character.Humanoid
end)スクリプトがインスタンスなしで進行できない場合は、使用する前にWaitForChild()で待機します:
local house1 = workspace:WaitForChild("House1")
local door = house1:WaitForChild("Door")リモートで送信されるインスタンス
RemoteEvent/RemoteFunction信号とそれが参照するインスタンスは独立して移動するため、信号がクライアントに到着する前にインスタンスが存在しない場合や、インスタンスがまったく存在しない場合があります。考えられる原因は次のとおりです:
ストリーミング下では、サーバー上でパーツ/モデルが作成されてからクライアントに複製されるまでにわずかな遅延がある場合があります。実際には、RemoteEvent/RemoteFunctionで参照されるパーツは、ストリーミングエリア内でさえ存在しない可能性があります。
サーバーからクライアントにRemoteEventまたはRemoteFunctionを介してパーツ/モデル参照を送信するには、インスタンスが受信クライアントに複製されている必要があります。インスタンスパスを文字列として送信することも同様の問題があり、パスがクライアント上の存在しない場所に解決される可能性があります:
クライアントスクリプトlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")remoteEvent.OnClientEvent:Connect(function(data)local checkpoint = data.checkpoint -- "checkpoint"がストリーミングされていない場合はエラーlocal level = workspace.Levels[data.levelPath] -- パスがストリーミングされていない場合はエラーend)
受信クライアントスクリプトがインスタンスを進行するために必要な場合は、使用する前にWaitForChild()を含めます。この場合、インスタンスが決してストリーミングされない場合は無限に待機する可能性があるため、WaitForChild()の第2引数としてタイムアウトを追加することを検討してください。
クライアントの非同期化
クライアント側の非同期化は例外として扱うべきであり、標準の設計パターンではありません。クライアント専用のコピーを導入したり、インスタンスをローカルで再親付けしたりすると、深刻な問題が発生する可能性があります。これらのタイプの変更がクライアントに持続することに依存しているコードを監査してください。
たとえば、ReplicatedStorageからWorkspaceにインスタンスをローカルで再親付けすると、そのインスタンスはストリーミングアウトされる資格を持つようになります。同様に、Instance:Clone()を使用してReplicatedStorageからWorkspaceにインスタンスをクローンすると、サーバーの複製パイプラインの一部ではなくなり、元のサーバー所有のインスタンスからのプロパティ更新を受け取らないクライアント専用のコピーが作成されます。
サーバー所有のオブジェクトに対してクライアントでInstance:Destroy()を呼び出す場合も同様の概念が適用されます。これにより、インスタンスはローカルで削除されますが、サーバーにはまだ存在するため、適格なときに元の状態で再度ストリーミングされます。
プロアクティブストリーミング
プレイヤーの次の目的地を予測できる場合は、サーバー側でPlayer:RequestStreamAroundAsync()を呼び出して、一時的な読み込みのために一時的なエリアをストリーミングします。また、明示的に解放されるまで読み込まれるべきエリアに対しては、Player:AddReplicationFocus()を制限された範囲で使用します。
たとえば、プレイヤーキャラクターが遠くの他のプレイヤーの家にCFrameを変更してテレポートしようとしている場合、ポップインを最小限に抑え、スムーズな遷移を提供するために目的地エリアを事前に取得できます。次のスクリプトは、事前取得メソッドを使用してプレイヤーキャラクターを移動させるためにクライアントからサーバーへのリモートイベントを発火させる方法を示しています。関数が返るときに事前取得リクエストが成功した場合、ターゲット位置の周囲の最小半径がクライアントに存在するはずです。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
local function teleportPlayer(player, teleportTarget)
-- ターゲット位置の周囲をストリーミングするリクエスト
player:RequestStreamAroundAsync(teleportTarget)
-- キャラクターをテレポート
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(teleportTarget))
end
end
-- クライアントがリモートイベントを発火させたときにテレポート関数を呼び出す
teleportEvent.OnServerEvent:Connect(teleportPlayer)インスタンスプロパティの読み取り
インスタンスがストリーミングアウトすると、そのプロパティの更新はそのクライアントに複製されなくなります。BasePart.Positionのようなプロパティを読み取ることは成功しますが、最後に複製された値を返し、これは任意に古くなる可能性があります。
local Players = game:GetService("Players")
local player = Players.LocalPlayer
-- "target"がストリーミングアウトしている場合、位置が古くなる可能性があります
local dist = (target.Position - player.Character.HumanoidRootPart.Position).Magnitudeロジックをサーバーに移動します。サーバー側のスクリプトは常にすべてのインスタンスを確認できます。これは、距離チェックや他の位置に敏感なロジックにとって一般的に最も信頼性の高いオプションです。
重要なパスでの待機
一部の非ストリーミングゲームは、ReplicatedStorageからWorkspaceにマップをクローンして読み込み、クライアントでそれを待機してからローディング画面を解除し、準備完了を通知します。ストリーミング下では、これは無限にハングします — クライアントのキャラクターがまだスポーンしていないため、複製フォーカスがなく、空間マップインスタンスは決してストリーミングされません。
ローディング画面のロジックを移動し、特定の空間インスタンスが存在することに依存しないようにします。たとえば、キャラクターがスポーンした後、周囲のエリアがストリーミングされたら準備完了を通知します。
信号の変化処理
Instance.ChildAdded/Instance.ChildRemovedやCollectionService信号(GetInstanceAddedSignal()やGetInstanceRemovedSignal()など)は、ストリーミングイン/アウト時にも発火し、実際のスポーン/削除と区別がつきません。受信スクリプトは信号だけでは違いを判断できないため、信号が「実際の」イベントに対応していると仮定するロジックは更新する必要があります。
ストリーミングインおよび/またはストリーミングアウトによってトリガーされた場合に壊れたり大きく変わったりする可能性のある信号リスナーを監査します。たとえば、敵NPCが世界に最初にスポーンしたときにオーディオや視覚効果を再生する場合、最初のスポーン時に各敵にSpawnedのような属性を割り当て、敵の将来のストリーミングイン時に同じオーディオ/効果を再生するのをスキップします。
local CollectionService = game:GetService("CollectionService")
local TAG_NAME = "Enemy"
CollectionService:GetInstanceAddedSignal(TAG_NAME):Connect(function(enemy)
if not enemy:GetAttribute("Spawned") then
-- 初回スポーン時に敵に"Spawned"属性を設定
enemy:SetAttribute("Spawned", true)
-- この初回スポーンのためにオーディオ/視覚効果を再生
playSpawnEffects(enemy)
end
end)コレクションの反復
クライアント側のコレクション反復(Instance:GetChildren()やInstance:GetDescendants()など)は、ストリーミングされた子孫のサブセットのみを返します。これは、親自体が常に複製される場合でも適用されます。たとえば、Workspaceの直下にあるFolderのように、その空間的な子孫がストリーミングインおよびアウトします。
local Players = game:GetService("Players")
local player = Players.LocalPlayer
-- フォルダ"Homes"は常に複製されますが、その子はストリーミングインおよびアウトします
-- このループは現在ストリーミングされていないホームを見逃す可能性があります
for _, home in workspace.Homes:GetChildren() do
if home.Settings.Owner.Value == player.Name then
return home
end
end完全な列挙が必要な場合は、サーバー上でスキャンを実行し、必要に応じてRemoteEventを介してプレイヤーに結果を渡します。
空間クエリ
クライアント側の空間クエリ(WorldRoot:Raycast()、WorldRoot:GetPartBoundsInBox()、Model:GetBoundingBox()など)は、ストリーミングされたコンテンツのみを反映します。これが問題かどうかは、クエリが何に使用されるかによります。
結果が完全な世界を反映する必要があるクエリにはサーバーを使用します。たとえば、プレイヤーが遠くのターゲットに視線を持っているかどうかを確認するレイキャストなどです。
その他のパターン
次のパターンも適用される可能性があり、慎重に考慮する必要があります:
3Dオブジェクトに親付けされたSoundまたはAudioPlayerは、そのオブジェクトがストリーミングアウトすると停止します。ストリーミングに関係なく持続するべき環境音のためには、エミッターを永続モデルまたは非ストリーミングコンテナに親付けします。
ゲーム内のUIオブジェクト(BillboardGuiやSurfaceGuiなど)や、BeamsやHighlightsのような視覚効果は、アドロニーやアタッチメントがストリーミングアウトすると単にレンダリングを停止します。これは意図された動作かもしれませんが、確認する必要があります。
BasePart.Touchedイベント、ProximityPrompts、DragDetectors、およびClickDetectorsは、関連するパーツ/モデルがストリーミングされていないクライアントのプレイヤーには機能しません。どの範囲からでもインタラクションが可能である必要がある場合、モデルは永続である必要があるか、インタラクションには別のメカニズムが必要です。
PathfindingServiceおよびクライアント側の経路探索では、パスファインダーはクライアント上でストリーミングされたジオメトリのみを認識し、サーバー上に存在する障害物を通過する可能性があります。戦略についてはこちらを参照してください。
現実的なテスト条件
スクリプトが更新されたら、ゲームを徹底的にテストします。ストリーミングバグは、ストリーミングエリアの端や遷移中にのみ現れることが多いため、スポーン近くやターゲット半径でのテストだけでは不十分です。
Workspace.StreamingTargetRadiusを最小値(64)に設定してテストします。ストリーミングバグは、ストリーミングエリアが小さいときにのみ現れることがあります。
ゲームの完全な移動パターンを通じてプレイし、遠くのエリア間でテレポートし、離れた後にエリアを再訪します。これらはストリーミングインおよびストリーミングアウトを最も多く行使する状況です。
ストリーミングデバッグオーバーレイを使用して、アクティブなストリーミング設定、現在読み込まれている領域、およびランタイムストリーミング状態を監視します。
OutputウィンドウとDeveloper Consoleでエラーを監視します。多くのスクリプトパターンは、静かに誤動作するのではなくエラーを生成します。特にattempt to index nil with ...という形式のエラーに注意してください。これは、しばしばWaitForChild()呼び出しが欠落していることを示しています。
Toolsを装備してアクティブにし、武器を発射し、さまざまなゲームインタラクションをトリガーします。
AIストリーミング変換スキル
ストリーミングの変換と最適化を支援するために、RobloxはスタジオのMCP サーバーからアクセスできるAIストリーミングスキルを提供しています。このスキルは、ゲームを自動的に評価し、推奨される設定を適用し、互換性の問題を解決します。これには以下が含まれます:
ゲームでAIスキルを使用するには:
- 重要ゲームのバックアップを取ります。変換プロセスは複雑になる可能性があるため、スキルを実行する前に必ずバックアップを保存してください(ファイル ⟩ Robloxに公開)。
スタジオのモデルコンテキストプロトコル(MCP)を介して、好みのLLMを使用してこのスキルを実行できます。大きなコンテキストウィンドウを持つ高性能AIモデルが推奨されます。Claude Opusでは、典型的な変換には20〜30分かかり、約200,000トークンのコンテキストを利用します。
- スタジオでゲームを開きます。
- スキルをダウンロードし、AIクライアントで解凍したフォルダー(roblox-streaming-conversion)を現在のプロジェクトとして開きます。
- /rbx-convert-to-streamingでスキルを実行します。
- すべてのAI出力と同様に、結果を確認し、現実的なテスト条件の下でゲームを広範囲にテストします。

