このガイドでは、ゲーム内でのインスタンスストリーミングを効率的かつ効果的に使用するためのいくつかのテクニックを概説します。「すべての解決策が一つに収束する」といったものはなく、ストリーミングゲームを設計したり、非ストリーミングゲームをストリーミングに変換したりする際に、これらのハイレベルな手順に従うと、大半のところまで進めることができます。
ストリーミングプロパティ
Studio内の Workspace オブジェクトの StreamingEnabled をオンにしたら、その関連プロパティを以下の推奨値に設定します:
| プロパティ | 推奨 |
|---|---|
| EnableSLIMAvatars | 適切な場合に標準リグアバターを軽量でアニメーションされたスタンドインとして表示するために Enabled を使用します。詳細についてはSLIMアバターを参照してください。 |
| ModelStreamingBehavior | Improved を使用して、 BasePart 子孫を持つ Models に対して最も効率的なストリーミングを有効にします。 |
| StreamingIntegrityMode | PauseOutsideLoadedArea を使用して、不要に長くなることや頻繁に停止することなくゲームプレイの整合性をバランスさせます。 |
| StreamingMinRadius | エンジンが低性能デバイス用にゲームを縮小できる最大量を最大化するためにデフォルトの 64 を使用します。 |
| StreamingTargetRadius | 高性能デバイスのプレイヤーに対する視認性と合理的なメモリフットプリントとの間で良好なバランスを取るためにデフォルトの 1024 を使用します。 |
| StreamOutBehavior | Opportunistic を使用して、クライアントがコンテンツを積極的にガーベッジコレクトできるようにし、メモリ使用量を大幅に削減し、メモリ不足によるクラッシュを引き起こすのを防ぎます。 |
モデルの詳細レベル
Model.LevelOfDetail は、ストリーミングされていない Model コンテンツに軽量のコンポジットまたはインポスターのメッシュを填補し、視覚的に世界を完全に見せるのに役立ちます。SLIM (スケーラブル軽量インタラクティブモデル) コンポジットメッシュは特に効果的です。プレイヤーは通常、SLIMメッシュを完全にストリーミングされたオリジナルと区別できないからです。
最良の結果を得るために:
- 空間的かつ論理的に関連する部品をまとめます。たとえば、車のすべての部品をまとめます。
- スタティックなメッシュとパーツを含むモデルには LevelOfDetail を SLIM に設定します。スキンメッシュを含むモデル、実行時に変更されるモデル、またはアニメーションを再生するモデルはサポートされません。
- 各モデルの空間的な範囲を ~64 立方スタッド以下に保つことで、実際のモデル全体が一緒にストリーミングされる可能性を高めます。モデルが非常に大きな範囲を持つ場合は、小さなモジュラーモデルに分割し、それぞれに適切な LevelOfDetail を適用します。
モデル構造
モデルの詳細レベルを設定するだけでなく、あなたの Models の構造と設定は、ストリーミングのパフォーマンスに大きな影響を与えます。既存のゲームをビルドする際や変換する際に:
論理的グループ化のために原子モデルを使用する — スクリプトがモデル内のすべての部品にアクセスする必要がある場合、その ModelStreamingMode を Atomic に設定します。これにより、クライアント側のスクリプトは、過剰に WaitForChild() を使用することなくモデル内のインスタンスに安全にアクセスできます(ただし、そのようなスクリプトは依然として全体の原子モデルについて WaitForChild() を使用する必要があります)。
永続モデルを最小限にする — 永続モデルは、参加後にロードされ、決してストリーミングアウトせず、メモリを永続的に占有します。モデルの ModelStreamingMode を Persistent に設定するのは、それが 常に スクリプトに利用可能でアクセス可能である必要がある場合に限ります。
SLIMアバター
現在ストリーミングされているエリアの外にあるプラットフォームアバターは、デフォルトでは表示されませんが、 Workspace.EnableSLIMAvatars を有効にすると、状況に応じて標準リグアバターを軽量なアニメーションスタンドインとしてレンダリングします。効果的には、エンジンは以下のことを行います:
- 実際のアバターモデルがストリーミングアウトされたときにSLIMバージョンをレンダリングします。
- ストリーミング半径内で、SLIMモデルはフル解像度モデルよりもレンダリングコストが大幅に低くなることができるため、SLIM/実際のモデルを利用可能なリソースに応じて交換します。
- シーンの重要性と帯域幅に応じてSLIMアニメーションを制限します。
アバターの詳細レベルシステムには、アバター向けの特定の最適化があります。以下では、SLIMによって処理されるものとされないものを説明します:
スクリプトパターン
以下のスクリプトパターンは、ストリーミングの影響を最も受けるものです。正しい戦略はコードの意図によって異なるため、各パターンには適切な場合に複数のオプションがリストされています。
子孫への直接インデックス
. 演算子で Workspace 子孫にインデックスを付けると、パス内のインスタンスが現在ストリーミングされていない場合、エラーが発生します。 FindFirstChild(), FindFirstChildWhichIsA()、および FindFirstChildOfClass() も同様で、子がストリーミングされていない場合 nil を返します。
子孫の検索local house1 = workspace:FindFirstChild("House1") -- "House1" がストリーミングされていない場合は nillocal 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 シグナルと、それが参照するインスタンスは独立して移動するため、シグナルはインスタンスが存在する前にクライアントに到着したり、インスタンスが全く存在しなかったりする可能性があります。考えられる2つの原因は以下の通りです:
ストリーミング中、サーバー上でパーツ/モデルが作成される時点とクライアントに複製される時点の間にわずかな遅延がある場合があります。実質的に、 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() の第二引数としてタイムアウトを追加することを検討してください。
クライアントの非同期化
クライアント側の非同期化は、標準の設計パターンではなく例外として扱われるべきです。クライアント専用のコピーを導入したり、インスタンスをローカルで再親付けすることは、深刻な問題を引き起こす可能性があります。クライアントにサーバーの複製によって変化が持続することを前提とするコードの監査を行ってください。
たとえば、リモートストレージのインスタンスをクライアント上で 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" がストリーミングアウトされた場合、Position は古くなっている可能性があります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() doif home.Settings.Owner.Value == player.Name thenreturn homeendend
完全な列挙が必要な場合は、サーバーでスキャンを行い、必要に応じて 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 を装備し、武器を発射し、異なるゲームのインタラクションをトリガーします。

