本指南概述了在游戏中高效和有效使用实例流式处理的几种技术。虽然没有“放之四海而皆准”的流式游戏设计解决方案,但遵循这些高层步骤将使您大致达到目标。
流式属性
一旦在Studio中为Workspace对象切换了StreamingEnabled,请将其相关属性设置为以下推荐值:
| 属性 | 推荐 |
|---|---|
| EnableSLIMAvatars | 在适当情况下使用Enabled将标准骨架化身渲染为轻量级、动画的替代品。有关更多信息,请参见SLIM化身。 |
| ModelStreamingBehavior | 使用Improved来启用具有BasePart后代的Models的最有效流式处理。 |
| 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()的第二个参数添加。
客户端不同步
客户端侧不同步应视为例外,而不是标准设计模式。引入仅限客户端的副本或在本地重新父级实例可能会导致严重问题。审计您的代码,查找依赖于这些类型更改在客户端上持续存在的地方。
例如,从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个上下文令牌。

