项目总览
技术方案文档
策划侧阅读文档
重构 · 结构拆分
重构 · 流程命令化
编辑器专栏
同图安全等待
统一安全落位设计(延期)
流送与 WP · 代码实现
V2 表现 · Seq2 → Seq3 衔接
传送点
流送与 WP · 代码实现
概念层(WP Cell、DataLayer 前缀、两盏灯的原理)在 横向专题 · 地图流送与 DataLayer 里已经讲透。 本页换一个视角:从八个代码文件出发,看一次场景加载或传送在流送这条线上到底排了哪些命令、 谁在驱动引擎、预加载为什么必须是三段式。
8 个文件
两盏灯
三段式预加载
假阳性
残留流送态
1. 全景图
下面这张图把「服务端就绪 → 命令组装 → 四步命令链 → 两盏灯流送 → 三段式预加载」串成一条线。 后面每一节都可以回到这张图上找到自己的位置。
2. 八个文件的分工
| 文件 | 职责 | 关键符号 |
|---|---|---|
| SceneLoadingController | 流送总控制器:服务端关卡就绪后的主加载,以及 WP DataLayer 的预加载与还原 / 取消 | OnServerLevelReady@98 · LoadSceneMain@111 · PreloadWpDataLayers@188 · FinishPreloadWpDataLayer@430 · RestorePreloadDataLayers@468 · CancelPreloadWpDataLayer@550 |
| SceneLoadingCommandHelper | 流送 / 传送命令的组装工厂:把「加载什么场景、怎么流送、怎么传送」翻译成命令 | TeleportInfo@40 · GetSceneLoadingType@78 · SetLoadingContextBornTransform@147 · GetFallbackCameraDirPos@234 · BuildSceneLoadingCommand@384 · BuildSceneTeleportCommand@425 |
| StreamingSourceInfo | 单个流送源的状态与位置:按帧更新位置、设置锁定状态,并判定流送是否完成 | EStreamingSourceState@11 · Tick@39 · UpdateStreamingSourcePosition@61 · SetStreamingLockStateAndPosition@70 · IsStreamingCompleted@200 |
| TransientStreamingSource | 临时流送源:支持跟随目标或固定在指定位置两种模式,可批量创建 / 推进 / 销毁 | InitFollowMode@43 · InitFixedMode@68 · CreateWithFollowTarget@376 · CreateAtPosition@419 · TickAll@546 · DestroyAll@574 |
| SceneWaitWpStreamingCmd | 命令链里的「等 WP 流送」节点:按是否需要地形流送决定等不等,持有所用的 timer 与 Promise | CancelStreamingWait@31 · WaitWpStreaming@46 · OnExecute@99 |
| SceneLoadingWpMapCmd | WP 地图加载命令:把主地图、等流送、加载期移动、等传送完成四步串成一条链 | OnAddInitCommand@27 · SceneLoadingMainMapCmd@43 · SceneWaitWpStreamingCmd@73 · SceneLoadingMoveCmd@76 · SceneLoadingWaitTeleportCmdFinishedCmd@100 |
| SceneLoadingTeleportCmd | 流送场景下的传送命令:判断是否在流送中、是否需要 Loading UI,并装配上下文初始化与等待流送 | isInSceneLoading@19 · needLoadingUi@23 · SceneLoadingWpMapCtxInitCmd@32 · SceneWaitWpStreamingCmd@59 |
| SceneLoadingWaitTeleportCmdFinishedCmd | 等待传送命令彻底结束,带 180 秒超时 | WaitTeleportTimeoutMs=180000@14 · HandleOnTeleportCmdFinished@64 · HandleWaitTimeout@70 · OnExecute@74 |
3. 主链路:一次加载排了哪些命令
1
服务端关卡就绪
SceneLoadingController.OnServerLevelReady@98 是客户端加载与「服务端已就绪」对齐的关键信号,收到后进入 LoadSceneMain@111 主加载。
2
组装命令
SceneLoadingCommandHelper 用 TeleportInfo@40 与 GetSceneLoadingType@78 判断这是换图、换层还是无缝,再由 BuildSceneLoadingCommand@384 / BuildSceneTeleportCommand@425 拼出命令。加载期出生点由 SetLoadingContextBornTransform@147 决定,相机兜底朝向由 GetFallbackCameraDirPos@234 决定。
3
装配四步链
SceneLoadingWpMapCmd.OnAddInitCommand@27 依次挂上:主地图加载(@43)→ 等 WP 流送(@73)→ 加载期移动(@76)→ 等传送完成(@100,180 秒超时)。
4
等流送
SceneWaitWpStreamingCmd 先看 Context.NeedsTerrainStreaming:非 SwitchDimension 需要等;SwitchDimension 里无缝换落点也要等;只有普通原地无缝才跳过。要等就走 WaitWpStreaming@46,最终等 GameModeController.CheckTargetStreamingCompleted。
5
流送源驱动
引擎侧的流送由两盏灯和临时源驱动,判定依据是
StreamingSourceInfo.IsStreamingCompleted@200。
6
收尾
等传送完成后归还临时流送源、清 timer;预加载过的 DataLayer 该还原还原、该取消取消。
4. 两盏灯:流送源怎么跟着走
项目里维护两类常驻流送源,加上一类句柄式临时源。
| 流送源 | 行为 | 位置 |
|---|---|---|
灯① StreamingSourceInfo | 跟随玩家位置,每帧用 Tick@39 / UpdateStreamingSourcePosition@61 更新 | 决定玩家脚下附近的 cell 什么时候被加载,是「平时」流送的主力 |
灯② TargetStreamingSourceInfo | 用 SetStreamingLockStateAndPosition@70 强制锁在指定位置(通常是传送目标落点) | 玩家还没到,先在目的地把地形流好,这是逐图流送的关键 |
临时源 TransientStreamingSource | 一次性使用:CreateWithFollowTarget@376 跟随某目标、CreateAtPosition@419 固定在某个位置,由 TickAll@546 批量推进 | 用完必须 DestroyAll@574 回收,否则源源不断产生流送开销 |
流送源的状态用 EStreamingSourceState@11 表示,从 Unload 到 LoadAndHide(载入但隐藏,对应 Loaded),
再到 LoadAndVisible(载入且可见,对应 Activated)。
假阳性:Activated 不等于碰撞就绪
cell 已经进入
Activated,不代表碰撞体、NavMesh 就绪了。
IsStreamingCompleted@200 的判定要和这个认知对齐,否则会把「看起来流完了其实还没好」当成引擎 bug。
详见 横向专题 · 假阳性。
5. 三段式预加载:预加载 → 完成 → 还原 / 取消
WP DataLayer 的预加载是三段式,缺一不可。
1
预加载
PreloadWpDataLayers@188 批量、PreloadWpDataLayer@180 单个,把请求挂成 IWpPreloadRequest(字段含 Id / Position / DataLayers / TargetDataLayerState / Promise / PollTimer / TimeoutTimer / IsCompleted),轮询目标层是否到达目标状态,并用超时 timer 兜底。
2
完成
FinishPreloadWpDataLayer@430 在达标后结算 Promise、标记 IsCompleted 并通知调用方。
3a
还原
RestorePreloadDataLayers@468 把预加载过的层还原回原状态。
3b
取消
CancelPreloadWpDataLayer@550 取消进行中的预加载并清掉 timer。
取消与还原都要做
只预加载而不还原、不取消,DataLayer 会一直停留在流送态,表现为「上一层图的状态漏到这一层」。
所以「预加载 → 完成 → 还原 / 取消」这三段必须成对出现。
6. 排查要点速记
- 卡在等流送:先看本轮是哪种
SceneLoadingType、是否使用服务端新落点;再看NeedsTerrainStreaming是否真需要等;最后确认流送灯有没有锁在正确位置。 - 取消旧等待:
CancelStreamingWait@31会标记 Cancelled、移除所持 timer,且只在模型字段仍指向该 timer 时才清字段——因为期间可能已有新的等待顶替上来,直接清任意计时器会误伤。 - 传送结束判定:
SceneLoadingWaitTeleportCmdFinishedCmd的 180 秒只属于「本命令开始执行后」的等待,不是整条流送链的全局截止;超时或创建计时器失败走 FailCritical,不会假装成功。 - 临时源泄漏:任何提前返回或异常分支都要经过
DestroyAll,否则临时源不回收会造成持续流送开销。