本文基于
UE5.6
版本的Lyra
中的CommonLoadingScreen
编写。
基本概念
CommonLoadingScreen
是一个用户快速创建Loading界面的插件,在Lyra项目中发布。
引入插件之后会附带一个ULoadingScreenManager
的UGameInstanceSubsystem
,会跟随GameInstance
进行初始化。
在ShouldCreateSubsystem
中判断了,如果是在DedicatedServer
则不创建。
在ULoadingScreenManager::Tick
中,会进行UpdateLoadingScreen
的判断从而更新Loading
的显隐状态。
Loading控制
核心是判断是否处于Loading状态,会判断各种需要Loading的情况,如果任意一个达成则认为需要展示Loading
(在此之前还会判断一些不需要Loading
的情况)。
判断不需要Loading的情况:
- 如果命令行设置了不展示
Loading
的NoLoadingScreen
,则认为不需要Loading
。 - 如果
GameVieportClient
处于nullptr
则不需要Loading
。
判断需要Loading的情况:
- 如果设置了
ForceLoadingScreenVisible
,则认为需要 - 如果从
GameInstance
取不到WorldContext
- 如果从
WorldContext
取不到World
- 如果取不到
GameState
- 如果地图处于加载状态
- 如果场景处于待传送状态
- 如果场景处于待连接网络状态
- 如果
World
还没有BeginPlay
- 如果
World
处于流式传送中 - 如果
GameState
重写了应该Loading
- 如果
GameState上
的Component
重写了应该Loading
- 如果设置的
ExternalLoadingProcessors
重写了应该Loading
- 如果
PlayerController
重写了应该Loading
- 如果
PlayerController
上的Component
重写了应该Loading
- 如果处于分屏游戏并且存在无效的
PlayerController
- 如果没有任何
PlaayerController
思路上,提供给用户自定义主要靠ILoadingProcessInterface
接口,可以实现是否应该Loading的判断,任意一个返回需要Loading
则最终会处于Loading
状态。
设计分析
判断地图加载状态
在ULoadingScreenManager::Initialize
中注册了加载地图前的事件PreLoadMapWithContext
与加载完成的事件PostLoadMapWithWorld
,从而在传送地图的时机进行一些处理。
注意
FCoreUObjectDelegates
的对象是进程级的,如果以多玩家的方式运行,此时有可能会触发非本GameInstance
的回调,需要判断World
的GameInstance
是否是本GameInstance
。
在PreLoadMapWithContext
时,标记此时处于LoadMap
状态。
|
|
在PostLoadMapWithWorld
时,标记此时已不处于LoadMap
状态。
输出为何处于Loading状态
在Tick
时会进行判断,每达到一个LogLoadingScreenHeartbeatInterval
(或者标记了LogLoadingScreenReasonEveryFrame
),将输出Loading
状态的原因(如果标记了LogLoadingScreenReasonEveryFrame
还会输出不需要的原因)。
为此,每次判断是否需要展示Loading
时,需要把原因记录在DebugReasonForShowingOrHidingLoadingScreen
中。
调整渲染设置
为了节约性能,在Loading
的时候会调整一些渲染的设置。
- ShaderPipelineCache::SetBatchMode设置为Fast,关闭时还原回Background
- 设置bDisableWorldRendering禁用世界渲染
- 设置处于HighPriorityLoading高优先级加载模式
- 调整线程心跳延迟系数
- 暂停主线程心跳判断
拦截Input
通过在创建Loading
时,注册了一个InputPreProcessor
来实现输入拦截。
|
|
调整线程心跳
在Loading
的控制流程里,通过调用FThreadHeartBeat
上的接口来控制线程心跳。
详情可参考:UnrealEngine 中的 ThreadHeartBeat 机制
界面刷新
通过调用一次SlateApplication
的Tick函数,来确保Loading
界面立刻刷新。
|
|
在界面隐藏时,会立即调用一次GC函数,确保Loading
界面销毁(感觉这个倒是意义不太明显)。
|
|
总结
设计思路上,CommonLoadingScreen
不是采用需要Loading
时调用Loading
,而是采用了判断需要Loading
状态时创建Loading
。这一设计有利于处理当同时有多个Loading
逻辑需要调用时,如何控制最终的Loading
状态。同时借助良好的Debug
埋点,可以方便的知道此时Loading
的具体原因。