技術與轉換

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

本指南概述了幾種有效和高效使用遊戲內 實例串流 的技術。雖然設計串流遊戲或將非串流遊戲轉換為串流沒有“一刀切”的解決方案,但遵循這些高層步驟將使您達到大部分目標。

串流屬性

StreamingEnabled 在 Studio 中為 Workspace 對象切換為開啟時,將其相關屬性設置為以下推薦值:

屬性建議
EnableSLIMAvatars使用 Enabled 在適當的情況下將標準機械人型角色渲染為輕量級、動態代替品。更多信息請參見 SLIM 角色
ModelStreamingBehavior使用 Improved 以啟用對含有 BasePart 後裔的 Models 最有效的串流。
StreamingIntegrityMode使用 PauseOutsideLoadedArea 以平衡遊玩完整性,而不會不必要或過於頻繁地暫停。
StreamingMinRadius使用默認值 64 最大化引擎在低端設備上縮放遊戲的能力。
StreamingTargetRadius使用默認值 1024 在高端設備的可見性和合理的內存佔用之間取得良好平衡。
StreamOutBehavior使用 Opportunistic 以允許客戶端積極垃圾回收內容,顯著減少內存使用並幫助防止內存不足崩潰。

模型詳細級別

Model.LevelOfDetail 協助填充非串流的 Model 內容,使用輕量級合成或冒名頂替的網格,使世界看起來視覺上完整。SLIM(可擴展輕量互動模型)合成網格特別有效,因為玩家通常無法區分 SLIM 網格與完全串流進來的原版。

為了獲得最佳效果:

  • 將空間上和邏輯上相關的部件進行分組,例如汽車的所有部件。
  • 對於包含 靜態 網格和部件的模型,將 LevelOfDetail 設置為 SLIM。包含蒙皮網格、在運行時被修改或播放動畫的模型不受支持。
  • 將每個模型的空間範圍保持在 ~64 立方 studs 內,以增加整個實際模型一起串流的可能性。如果模型具有非常大的範圍,則將其拆分為較小的模組模型,並為每個模型應用適當的 LevelOfDetail

模型結構

除了設置 模型詳細級別 之外,您的 Models 的結構和設置對串流的性能有重大影響。在您建立或轉換現有遊戲時:

  • 使用原子模型進行邏輯分組——當腳本需要訪問模型中的所有部件時,將其 ModelStreamingMode 設置為 Atomic。這使客戶端腳本能夠安全地訪問模型內的實例,而不過度使用 WaitForChild()(雖然此類腳本仍然必須對整體原子模型使用 WaitForChild())。

  • 最小化持久模型——持久 模型在加入後加載,並且永遠不會串流出,永遠佔用內存。僅當模型 必須 始終對腳本可用和可訪問時,將其 ModelStreamingMode 設置為 Persistent

  • 分解容器模型——一種常見的非串流模式是單個巨大 Model,其中包含許多 NPC、道具或類似的分組。在串流下,容器模型會降低串流效率,並且對於 模型詳細級別 的性能也不理想,後者更適合密切分組的實例。將容器模型分解為更小的模型,並包含物理上接近或邏輯上相關的部件。

  • 扁平化深度嵌套的模型層次結構——在 原子 模型內嵌套 持久 模型有效地強制原子模型表現為持久的。扁平化的層次結構在串流下更容易理解。

SLIM 角色

當前串流區域外的平台角色默認是不可見的,但啟用 Workspace.EnableSLIMAvatars 時會在適當的情況下將 標準機械人 渲染為輕量級、動態的代替品。實際上,該引擎:

  • 當實際角色模型串流出時,渲染 SLIM 版本。
  • 根據串流半徑內可用資源在 SLIM/實際之間進行交換,因為 SLIM 模型的渲染成本可能遠低於全分辨率的模型。
  • 根據場景重要性和帶寬限制 SLIM 動畫。

角色詳細級別系統為角色進行了特定的優化。以下描述了 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 引用的部件可能根本還不存在,即使在串流區域內。

  • 通過 RemoteEventRemoteFunction 將部件/模型引用從服務器發送到客戶端需要該實例被複製到接收客戶端。通過字符串發送實例路徑也有相同的問題,因為路徑可能解析到在客戶端上不存在的位置:

    客戶端腳本

    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 可能使該實例有資格被串流出去。同樣,從 ReplicatedStorageWorkspace 中本地複製實例(Instance:Clone())會創建僅限客戶端的副本,該副本將不再成為服務器複製管道的一部分,因此無法從原始的服務器擁有實例接收屬性更新。

當對服務器擁有的對象調用 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.ChildRemovedCollectionService 信號如 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 物體的 SoundAudioPlayer 在該物體串流出去時停止。對於應該無論串流情況如何都持續存在的環境音頻,請將發射器附加到 持久 模型或非串流容器中。

  • 遊戲內 UI 對象如 BillboardGuiSurfaceGui 以及 視覺效果BeamsHighlights,當其被附加的物體或連接物串流出去時將停止渲染。這可能是預期的行為,但您應自行確認。

  • BasePart.Touched 事件、ProximityPromptsDragDetectorsClickDetectors 不會針對未能在客戶端上串流相應部件/模型的玩家操作。如果必須在任何范圍內都可以交互,則模型需要是 持久 的,或者交互需要使用不同的機制。

  • 對於 PathfindingService 和客戶端路徑尋找,路徑尋找者僅能在客戶端上看到串流進來的幾何圖形,並且它可能通過服務器上存在的障礙物進行路由。請參見 此處 獲取策略。

現實測試條件

一旦 腳本更新,對遊戲進行徹底測試。串流錯誤通常僅在串流區域的邊緣或過渡期間才會顯現,因此僅在生成附近或在目標半徑內測試是不夠的。

  • 測試時將 Workspace.StreamingTargetRadius 設置為最小值 (64)。某些串流錯誤僅在串流區域較小時出現。

  • 逐步玩遍遊戲的完整遍歷模式,傳送至遙遠區域,並在離開區域後重新訪問這些區域。這些都是施加串流進出最多的情況。

  • 使用 串流調試疊加層 監控當前活動的串流設置、當前加載的區域和運行時串流狀態。

  • 觀察 輸出 窗口和 開發者控制台 中的錯誤,因為許多 腳本模式 會產生錯誤而非靜默的錯誤行為。特別注意 attempt to index nil with ... 形式的錯誤,這通常表明 missing WaitForChild() 調用。

  • 裝備和啟用 Tools,發射武器並觸發不同的遊戲交互。

©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。