UE5中的CommonLoadingScreen插件

文章字数:2145

本文基于UE5.6版本的Lyra中的CommonLoadingScreen编写。

基本概念

CommonLoadingScreen是一个用户快速创建Loading界面的插件,在Lyra项目中发布。

引入插件之后会附带一个ULoadingScreenManagerUGameInstanceSubsystem,会跟随GameInstance进行初始化。

ShouldCreateSubsystem中判断了,如果是在DedicatedServer则不创建。

ULoadingScreenManager::Tick中,会进行UpdateLoadingScreen的判断从而更新Loading的显隐状态。

Loading控制

核心是判断是否处于Loading状态,会判断各种需要Loading的情况,如果任意一个达成则认为需要展示Loading(在此之前还会判断一些不需要Loading的情况)。

判断不需要Loading的情况:

  • 如果命令行设置了不展示LoadingNoLoadingScreen,则认为不需要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的回调,需要判断WorldGameInstance是否是本GameInstance

PreLoadMapWithContext时,标记此时处于LoadMap状态。

1
bCurrentlyInLoadMap = true;

PostLoadMapWithWorld时,标记此时已不处于LoadMap状态。

输出为何处于Loading状态

Tick时会进行判断,每达到一个LogLoadingScreenHeartbeatInterval(或者标记了LogLoadingScreenReasonEveryFrame),将输出Loading状态的原因(如果标记了LogLoadingScreenReasonEveryFrame还会输出不需要的原因)。

为此,每次判断是否需要展示Loading时,需要把原因记录在DebugReasonForShowingOrHidingLoadingScreen中。

调整渲染设置

为了节约性能,在Loading的时候会调整一些渲染的设置。

  • ShaderPipelineCache::SetBatchMode设置为Fast,关闭时还原回Background
  • 设置bDisableWorldRendering禁用世界渲染
  • 设置处于HighPriorityLoading高优先级加载模式
  • 调整线程心跳延迟系数
  • 暂停主线程心跳判断

拦截Input

通过在创建Loading时,注册了一个InputPreProcessor来实现输入拦截。

1
2
InputPreProcessor = MakeShareable<FLoadingScreenInputPreProcessor>(new FLoadingScreenInputPreProcessor());  
FSlateApplication::Get().RegisterInputPreProcessor(InputPreProcessor, 0);

调整线程心跳

Loading的控制流程里,通过调用FThreadHeartBeat上的接口来控制线程心跳。

详情可参考:UnrealEngine 中的 ThreadHeartBeat 机制

界面刷新

通过调用一次SlateApplication的Tick函数,来确保Loading界面立刻刷新。

1
2
// Tick Slate to make sure the loading screen is displayed immediately  
FSlateApplication::Get().Tick();

在界面隐藏时,会立即调用一次GC函数,确保Loading界面销毁(感觉这个倒是意义不太明显)。

1
GEngine->ForceGarbageCollection(true);

总结

设计思路上,CommonLoadingScreen不是采用需要Loading时调用Loading,而是采用了判断需要Loading状态时创建Loading。这一设计有利于处理当同时有多个Loading逻辑需要调用时,如何控制最终的Loading状态。同时借助良好的Debug埋点,可以方便的知道此时Loading的具体原因。

该内容采用 CC BY-NC-SA 4.0 许可协议。

如果对您有帮助或存在意见建议,欢迎在下方评论交流。

加载中...