このガイドでは、経験内でのインスタンスストリーミングを効率的かつ効果的に使用するためのさまざまなテクニックを説明します。
ストリーミングのために意図的にデザインする
ストリーミングは無料ではありません。裏では、ネットワーク、状態追跡、動的コンテンツ管理のための複雑なエンジン全体のシステムが関与しています。ストリーミングの利点を最大限に活かすためには、ゲームプレイの流れやプレイヤーのインタラクションパターンを明確に理解した上で体験をデザインすることが重要です。効率的なストリーミングには、データモデルでの意図的なコンテンツ構造と、インスタンスの可用性が必要なタイミングや場所を慎重に考慮する必要があります。
ストリーミングの範囲を理解する
ストリーミングのロジックと機能は、Workspaceの子孫であるインスタンスにのみ適用され、ReplicatedStorageやReplicatedFirstなどの他のコンテナに格納されているインスタンスはストリーミングの対象外です。たとえば、アトミックモデルをReplicatedStorageの下に置いても、アトミックなレプリケーションは保証されません。
モデルを適切に整理する
多くの子孫を持つModelsは、エンジンにとって更新や維持にコストがかかるため、そのようなモデルは小さくモジュラーなモデルに分解することをお勧めします。これにより、エンジンはストリーミング操作をより効率的に処理でき、体験の構造の明確さと整理整頓が向上します。
たとえば、すべてのNPCを単一のアトミックモデルに配置し、その中にインスタンスを動的に追加または削除することは推奨されません。代わりに、各NPCとその関連スクリプトやアセットを自己完結型のモデルにカプセル化して、個別のキャラクターをストリーミングする際の整理整頓とモジュラリティを向上させることを検討してください。
論理的グルーピングのためにアトミックモデルを使用する
インスタンスのグループが常に一緒に表示される必要がある場合(たとえば、インタラクティブな小道具や建物など)、それらをアトミックモデルにカプセル化します。アトミックモデルは、初期子孫がすべてストリーミングされた後にのみクライアントのWorkspaceに親子関係が設定されるため、アトミックモデル内のインスタンスが存在することで、すべての初期子孫が利用可能になります。これにより、クライアント側のスクリプトは、過度にWaitForChild()を使用することなく、アトミックモデル内のインスタンスに安全にアクセスできます。
ただし、そのようなクライアント側のスクリプトも、全体のアトミックモデルに対してはWaitForChild()を使用する必要があることに注意してください。ただし、モデルが利用可能になれば、初期子孫に対してWaitForChild()を呼び出す必要はありません。
永続モデルの使用を最小限に抑える
永続モデルは、参加時にストリーミングされ、決してストリーミングアウトされることはありません。永続モデルの過度の使用は、パフォーマンスの劣化やメモリ圧力につながる可能性があります。モデルとその子孫がクライアントに常に利用可能であることが不可欠な場合にのみ、永続モデルを使用してください。
たとえば、ゲームプレイの間に「場合によっては」参照される可能性のあるすべてのメッシュや部品を含む大きな永続モデルを定義することは効果的なデザイン戦略ではありません。代わりに、論理的に関連する部品やインスタンスを小さなアトミックモデル(またはそれが望ましい場合は永続モデル)にグループ化して、エンジンがそれらを独立したユニットとしてストリーミングイン/アウトできるようにしてください。
ネストモデルに注意する
深くネストされたモデルは、意図したストリーミング動作に干渉する可能性があります。たとえば、永続モデルをアトミックモデル内に含めることは、実質的にアトミックモデル全体を永続にすることを強制します。可能な限り、モデルの階層をフラット化して結合を減らし、デバッグを簡素化し、意図しない動作を避けるようにしてください。
クライアントの非同期状態に注意する
クライアント側の非同期状態は例外として扱うべきであり、標準的なデザインパターンではありません。クライアント専用のコピーを導入したり、インスタンスをローカルで親子関係を変更することは、深刻な問題を引き起こす可能性があります。
たとえば、ReplicatedStorageからWorkspaceにインスタンスをローカルで親子関係を変更すると、そのインスタンスはストリーミングアウトされる資格が得られる可能性があります。同様に、ReplicatedStorageからWorkspaceにインスタンスをローカルでクローン(Instance:Clone())すると、クライアント専用のコピーが作成され、元のサーバー所有のインスタンスからのプロパティ更新を受け取らなくなります。
タイミング遅延を理解する
サーバー上で部品が作成されてからクライアントにレプリケートされるまでに、約10ミリ秒のわずかな遅延が生じることがあります。以下の各シナリオでは、部分ストリーミングと同時にイベントやプロパティ更新が常に発生するとは仮定せずに、WaitForChild()や他の技術を使用する必要があるかもしれません。
| シナリオ | ストリーミング動作 |
|---|---|
| クライアント側のスクリプトがサーバーにRemoteFunctionを呼び出してBasePartを作成します。 | RemoteFunctionがクライアントに戻るとき、BasePartはまだ存在しない可能性があります。ストリーミングされた領域内にあってもです。 |
| サーバー側のスクリプトがRemoteEventを使用して、クライアント上のプレイヤーキャラクターに非常に近くでBasePartを作成します。 | RemoteEvent信号がクライアントに受信されたとき、BasePartはまだ存在しない可能性があります。ストリーミングされた領域内にあってもです。 |
インスタンスストリーミングを検出する
ストリーミングは主に3D空間の近接性に基づいているため、Workspace以下のすべてのインスタンスがクライアントで常に利用可能であるとは仮定できません。インスタンスにアクセスする前にその存在を常に検証し、オブジェクトのストリーミングインまたはアウトを検出して対処する必要があります。ストリーミングの検出には、以下のような便利なパターンがあります。
インスタンスのプロパティのタグセクションを使用して、影響を受けるすべてのオブジェクトに論理的なCollectionServiceタグを割り当てます。
シングルクライアント側スクリプトから、タグ付けされたオブジェクトがGetInstanceAddedSignal()およびGetInstanceRemovedSignal()を介してストリーミングインまたはアウトするときに検出し、オブジェクトを適切に処理します。たとえば、以下のコードは、タグ付けされたLightオブジェクトをストリーミングインするときに「点滅」ループに追加し、ストリーミングアウトするときに削除します。
クライアントスクリプト - CollectionServiceストリーミング検出local CollectionService = game:GetService("CollectionService")local tagName = "FlickerLightSource"local random = Random.new()local flickerSources = {}-- 現在および新たにタグが付けられた部品がストリーミングインまたはアウトするのを検出するfor _, light in CollectionService:GetTagged(tagName) doflickerSources[light] = trueendCollectionService:GetInstanceAddedSignal(tagName):Connect(function(light)flickerSources[light] = trueend)CollectionService:GetInstanceRemovedSignal(tagName):Connect(function(light)flickerSources[light] = nilend)-- 点滅ループwhile true dofor light in flickerSources dolight.Brightness = 8 + random:NextNumber(-0.4, 0.4)endtask.wait(0.05)end
機会的ストリームアウトを使用する
OpportunisticモードのWorkspace.StreamOutBehaviorは、クライアントがWorkspace.StreamingTargetRadiusの外部のコンテンツを積極的にガベージコレクトできるようにします。これにより、特にローエンドデバイスでのメモリ使用量が大幅に削減され、クラッシュやメモリ不足の条件を防ぐのに役立ちます。Opportunisticモードは、大規模またはコンテンツが豊富な体験が多様なハードウェア全体でパフォーマンスを維持する必要がある場合に最適です。
モデルの詳細レベルを設定する
現在ストリーミングされている領域外のModelsはデフォルトでは表示されません。ただし、存在しないクライアント向けに、軽量な「SLIM」メッシュまたは低解像度の「インポスター」メッシュをモデルとしてレンダリングするようにエンジンに指示できます。この動作は、各モデルのLevelOfDetailプロパティを通じて制御します。
非デフォルトのLevelOfDetailプロパティを持つモデルについては、Robloxは、モデルが不完全である場合、ストリーミング半径内であってもSLIMまたはインポスターメッシュを表示します; モデルから欠落しているインスタンスによりLODが表示されます。これらのモデルのゲーム内スペースを64立方スタッド以下に保つことで、全体のモデルが一緒にストリーミングされる可能性が高まり、またEnum.ModelStreamingMode.Atomic設定を使用して、一緒にストリーミングされるようにします。さらに、クライアントでこれらのモデルを変更しないようにしてください。どんな変更もモデルを不完全と見なす原因となります。




| モデル設定 | ストリーミング半径外の動作 |
|---|---|
| SLIM | オリジナルモデルがクライアントに存在しない場合、モデル内のすべての子部品の複合メッシュが表示されます。Robloxは、モデルに対して多くのSLIM(スケーラブル軽量インタラクティブモデル)メッシュを自動的に生成し、カメラからの距離が増すにつれて徐々に複雑さが低下します。
|
| StreamingMesh | クライアントにモデルが存在しないときに、インポスターメッシュが表示されます。
|
| Disabled / Automatic | モデルはストリーミング半径にプレイヤーが入るまで存在せず、プレイヤーが半径を離れた後はいつでもストリーミングアウトできます。 |
アバターの詳細レベルを設定する
現在ストリーミングされている領域外のプラットフォームアバターはデフォルトでは表示されません。ただし、エンジンにプラットフォームアバターモデルを標準リグでレンダリングし、最適化された軽量「SLIM」アバター表現として補助的な衣服やレイヤーを含む任意の数のアクセサリを持たせるように指示できます。
Workspace.EnableSLIMAvatarsプロパティをインスタンスストリーミングとともに有効にすると、各複合SLIMモデルがクラウド内で複数のLODを生成します。これにより、エンジンは自動的に次のことを行います。
- ストリーミング半径の外部でプラットフォームアバターがストリーミングアウトされるときにSLIMバージョンをレンダリングします。
- ストリーミング半径内のプラットフォームアバターが利用可能なリソースに応じて、SLIMバージョンまたはフルモデルのレンダリングを切り替えます。
- シーンの重要性と利用可能なネットワーク帯域幅に基づいて、SLIMアバターのアニメーション更新を制御します。
これらのパフォーマンス最適化により、視覚的に満たされたソーシャルスペースや混雑したイベントを実現し、さまざまなデバイス機能にわたってスケーリングできます。最良の視覚体験を保証するために、ワールドモデルのLevelOfDetailプロパティをSLIMに設定することも忘れないでください。
アバターの詳細レベルシステムには、アバターに特定の最適化があります。以下は、SLIMによって処理されるものとそうでないものを示します:
含まれる:
- ストリーミングが有効な状態でプレイヤーがあなたの経験に参加するときにRobloxによって追加される標準リグを持つ任意のアバター。これには、キャラクターの体、頭、レイヤード衣服、アクセサリが含まれます。
- Player.CharacterAdded()信号の発火時とPlayer.CharacterAppearanceLoaded()信号の発火前に、プレイヤーのアバターに加えられた変更。
除外される:
- R6アバター。
- NPC(ノンプレイヤーキャラクター)、たとえそれに標準リグが含まれていても。
- 比率や衣服、体のパーツを変更するカスタムアバター設定。その代わりに、アバターモデルがクライアントにストリーミングされるときに元の「プレイヤーの選択」アバターがレンダリングされます。
- Player.CharacterAppearanceLoaded()信号が発火した後のアバターモデルの変更は、SLIMモデルには反映されません。これには、次のような変更が含まれます:
- アバターモデルやその外観を修正するスクリプト。
- アバターがツールを装備する。
- アバターがアクセサリや衣服を追加または削除する。
- アバターに追加されたハイライト。
ストリーミング半径を戦略的に使用する
Workspace.StreamingMinRadiusプロパティは、正しいゲームプレイを開始または継続するために読み込まれなければならないエリアを定義します。この最小半径は、参加時間や体験中のパフォーマンスに大きな影響を与え、特に半径が大きく過剰なコンテンツが含まれている場合には顕著です。
Workspace.StreamingTargetRadiusプロパティは、エンジンがランタイムセッションを通じてストリーミングインしようとする広範囲のエリアを定義します。最小半径に比べて、ターゲット半径内のコンテンツは優先度が低く、ゲームプレイをブロックすることなく段階的に読み込むことができます。
この動作は戦略的に活用できます。Workspace.StreamingTargetRadiusを大きめに設定し、顕著なゲームプレイの問題や視覚的不整合を回避しますが、サーバーが不要なコンテンツをストリーミングするほど大きくすることは避けてください。
ストリーミングの整合性を活用する
Workspace.StreamingIntegrityModeプロパティは、十分なコンテンツがストリーミングされているかどうかに応じて、クライアントが一時停止するかどうかを制御します。これにより、キャラクターが読み込まれていない地形を通過する、プレイヤーがアクセスしてはいけない領域を見える、またはプレイヤーが部分的に読み込まれた環境と相互作用するなどの状況を防ぎます。
ほとんどのシナリオでは、PauseOutsideLoadedAreaオプションが推奨されます。これにより、初期読み込みや重いストリーミング変遷の際の決定論的な動作が保証され、ストリーミングされた世界が不完全な場合の予測不能なゲームプレイを最小限に抑えることができます。
プロアクティブにストリーミングする
プレイヤーの動きはしばしば予測可能です。次の目的地が予測できる場合は、サーバー側でPlayer:RequestStreamAroundAsync()を呼び出して、一時的にロードできるストリーミングインエリアに入り、Player:AddReplicationFocus()を使用して、明示的に解放されるまでロードされたままにしておく必要があるエリアを定義します。
たとえば、プレイヤーが他のプレイヤーの家に訪問しようとしている場合、目的地エリアをRequestStreamAroundAsync()を使用して事前に取得し、ポップインを最小限に抑え、スムーズな遷移を提供します。
頻繁に訪問されるエリアに対しては、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)
クライアントスクリプト - リモートイベントを発火local ReplicatedStorage = game:GetService("ReplicatedStorage")local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")local teleportTarget = Vector3.new(50, 2, 120)-- リモートイベントを発火させるteleportEvent:FireServer(teleportTarget)
ポーズ画面をカスタマイズする
プレイヤーがあなたの体験でストリーミングの一時停止に遭遇したときのために、ポーズ画面をカスタマイズすることを検討してください。Player.GameplayPausedプロパティは、プレイヤーの現在のポーズ状態を示し、GetPropertyChangedSignal()との接続に使用してカスタムGUIを表示または非表示にすることができます。
LocalScript
local Players = game:GetService("Players")
local GuiService = game:GetService("GuiService")
local player = Players.LocalPlayer
-- デフォルトのポーズモーダルを無効にする
GuiService:SetGameplayPausedNotificationEnabled(false)
local function onPauseStateChanged()
if player.GameplayPaused then
-- カスタムGUIを表示
else
-- カスタムGUIを非表示
end
end
player:GetPropertyChangedSignal("GameplayPaused"):Connect(onPauseStateChanged)
画面上のデバッグを使用する
エンジンには、キーボードショートカットを使用してクライアント上で有効にできる複数の画面上のデバッグパネルが含まれています。ストリーミングデバッグ情報にアクセスするには:
ShiftCtrlF3(Windows)またはShift⌘F3(Mac)でネットワーク概要デバッグオーバーレイを開きます。
デバッグオーバーレイが開いたら、Shift1を繰り返し押して利用可能なパネルを切り替えます。4番目のパネルはストリーミングデバッグビューで、役立つランタイム情報を表示します:
- アクティブなストリーミング設定
- 現在ロードされている(ストリーミングインされた)領域
- ストリーミングの動作と状態
このパネルを使用して、ゲームプレイ中にどのようにコンテンツがストリーミングされているかを検査し、奇妙なゲームプレイの動作やストリーミング領域が欠如している問題を診断するのに役立ててください。