本指南概述了幾種有效和高效使用遊戲內 實例串流 的技術。雖然設計串流遊戲沒有「一刀切」的解決方案,但遵循這些高層次的步驟將使您大致達到目標。
串流屬性
一旦在 Studio 中為 Workspace 對象切換 StreamingEnabled,請將其相關屬性設置為以下建議值:
| 屬性 | 建議 |
|---|---|
| EnableSLIMAvatars | 在適當的情況下,使用 Enabled 將標準骨架化身渲染為輕量級的動畫替代品。更多信息請參見 SLIM 角色。 |
| ModelStreamingBehavior | 使用 Improved 來啟用對於具有 BasePart 後代的 Models 的最有效串流。 |
| StreamingIntegrityMode | 使用 PauseOutsideLoadedArea 來平衡遊戲性完整性,而不會不必要或過於頻繁地暫停。 |
| StreamingMinRadius | 使用默認值 64 來最大化引擎可以為低端設備縮小遊戲的程度。 |
| StreamingTargetRadius | 使用默認值 1024 來在高端設備的玩家可見性和合理的內存佔用之間取得良好平衡。 |
| StreamOutBehavior | 使用 Opportunistic 來允許客戶端積極地垃圾收集內容,顯著減少內存使用並幫助防止內存不足崩潰。 |
模型細節層次
Model.LevelOfDetail 有助於用輕量級的合成或替代網格填充未串流的 Model 內容,使世界看起來視覺上完整。 SLIM(可擴展輕量級互動模型)特別有效,因為玩家通常無法區分 SLIM 網格和完全串流進來的原始模型。
為了獲得最佳效果:
- 將空間上和邏輯上相關的部件分組,例如汽車的所有部件。
- 將每個模型的空間範圍保持在 ~64 立方 studs 以增加整個實際模型一起串流的可能性。如果模型的範圍非常大,請將其拆分為更小的模塊模型,並對每個模型應用適當的 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() 的第二個參數添加。
客戶端不同步
客戶端側不同步應被視為例外,而不是標準設計模式。引入僅限客戶端的副本或在本地重新父級實例可能會造成嚴重問題。審核您的代碼,查找依賴於這些類型的更改在客戶端持久存在的地方。
例如,將實例從 ReplicatedStorage 本地重新父級到 Workspace 可能使該實例有資格被串流出。同樣,從 ReplicatedStorage 本地克隆實例(Instance:Clone())到 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)。某些串流錯誤僅在串流區域較小時出現。
完整地玩遍遊戲的所有遍歷模式,在遙遠的區域之間傳送,並在離開後重新訪問區域。這些是最能檢驗串流進和串流出的情況。
使用 串流調試覆蓋 來監控活動的串流設置、當前加載的區域和運行時串流狀態。
觀察 輸出 窗口和 開發者控制台 中的錯誤,因為許多 腳本模式 會產生錯誤而不是靜默的錯誤行為。特別注意 attempt to index nil with ... 形式的錯誤,這通常表示缺少 WaitForChild() 調用。
裝備並激活 Tools,開火武器,觸發不同的遊戲交互。
AI 串流轉換技能
為了協助串流轉換和優化,Roblox 提供了一個 AI 串流技能,可從 Studio MCP 服務器 訪問。該技能自動評估您的遊戲,應用建議的配置,並清理兼容性問題,包括:
要在您的遊戲中使用 AI 技能:
- 重要備份您的遊戲。轉換過程可能很複雜,因此您應該 始終 保存備份(文件 ⟩ 發布 到 Roblox 作為)然後再運行該技能。
您可以通過 Studio 中的 模型上下文協議 (MCP) 使用任何您喜歡的 LLM 運行此技能。建議使用具有大上下文窗口的高端 AI 模型;在 Claude Opus 中,典型的轉換需要 20-30 分鐘,並使用大約 200,000 個上下文標記。

