UI
在这个全面的概述中,开始学习Chirpy的基础知识。您将学习如何安装、配置和使用您的第一个基于Chirpy的网站,以及将其部署到web服务器。
UI
UI框架
配置关于UI的ini文件
DefaultGame.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[/Script/LyraGame.LyraUIManagerSubsystem]
DefaultUIPolicyClass=/Game/UI/B_LyraUIPolicy.B_LyraUIPolicy_C
[/Script/LyraGame.LyraUIMessaging]
ConfirmationDialogClass=/Game/UI/Foundation/Dialogs/W_ConfirmationDefault.W_ConfirmationDefault_C
ErrorDialogClass=/Game/UI/Foundation/Dialogs/W_ConfirmationError.W_ConfirmationError_C
[/Script/CommonLoadingScreen.CommonLoadingScreenSettings]
LoadingScreenWidget=/Game/UI/Foundation/LoadingScreen/W_LoadingScreen_Host.W_LoadingScreen_Host_C
ForceTickLoadingScreenEvenInEditor=False
[/Script/CommonInput.CommonInputSettings]
InputData=/Game/UI/B_CommonInputData.B_CommonInputData_C
bEnableInputMethodThrashingProtection=True
InputMethodThrashingLimit=30
InputMethodThrashingWindowInSeconds=3.000000
InputMethodThrashingCooldownInSeconds=1.000000
bAllowOutOfFocusDeviceInput=True
[/Script/CommonUI.CommonUISettings]
bAutoLoadData=True
DefaultImageResourceObject=None
DefaultThrobberMaterial=/Game/UI/Foundation/Materials/M_UI_Throbber_Base.M_UI_Throbber_Base
DefaultRichTextDataClass=/Game/UI/Foundation/RichTextData/CommonUIRichTextData.CommonUIRichTextData_C
+PlatformTraits=(TagName="Platform.Trait.SupportsWindowedMode")
+PlatformTraits=(TagName="Platform.Trait.CanExitApplication")
+PlatformTraits=(TagName="Platform.Trait.SupportsChangingAudioOutputDevice")
+PlatformTraits=(TagName="Platform.Trait.SupportsBackgroundAudio")
+PlatformTraits=(TagName="Platform.Trait.Input.SupportsMouseAndKeyboard")
+PlatformTraits=(TagName="Platform.Trait.Input.HardwareCursor")
+PlatformTraits=(TagName="")
+PlatformTraits=(TagName="Platform.Trait.NeedsBrightnessAdjustment")
+PlatformTraits=(TagName="Platform.Trait.ReplaySupport")
+PlatformTraits=(TagName="Platform.Trait.SupportsLatencyStats")
+PlatformTraits=(TagName="Platform.Trait.SupportsLatencyMarkers")
CommonButtonAcceptKeyHandling=Ignore
[CheatScript.DebugUI]
+Cmd=Log LogSlate VeryVerbose
+Cmd=Log LogUMG VeryVerbose
+Cmd=Log LogCommonUI VeryVerbose
+Cmd=Log LogCommonInput VeryVerbose
+Cmd=Log LogUIActionRouter VeryVerbose
+Cmd=SlateDebugger.Start
DefaultEngine.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[DistillSettings]
+FilesToAlwaysDistill=UI/*
[/Script/Engine.Engine]
GameViewportClientClassName=/Script/LyraGame.LyraGameViewportClient
LocalPlayerClassName=/Script/LyraGame.LyraLocalPlayer
[/Script/Engine.UserInterfaceSettings]
RenderFocusRule=Never
HardwareCursors=()
SoftwareCursors=((Default, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(TextEditBeam, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(ResizeLeftRight, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(ResizeUpDown, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(ResizeSouthEast, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(ResizeSouthWest, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(CardinalCross, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(Crosshairs, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(Hand, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(GrabHand, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(GrabHandClosed, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(SlashedCircle, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"),(EyeDropper, "/Game/UI/Foundation/SoftwareCursors/W_ArrowCursor.W_ArrowCursor_C"))
ApplicationScale=1.000000
UIScaleRule=ScaleToFit
CustomScalingRuleClass=None
UIScaleCurve=(EditorCurveData=(Keys=((Time=480.000000,Value=0.444000),(Time=720.000000,Value=0.666000),(Time=1080.000000,Value=1.000000),(Time=8640.000000,Value=8.000000)),DefaultValue=340282346638528859811704183484516925440.000000,PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant),ExternalCurve=None)
bAllowHighDPIInGameMode=False
DesignScreenSize=(X=1920,Y=1080)
bLoadWidgetsOnDedicatedServer=True
[/Script/Engine.CollisionProfile]
+Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
[ConsoleVariables]
gpad.DefaultLeftStickInnerDeadZone=0.24
gpad.DefaultRightStickInnerDeadZone=0.27
[EnumRemap]
TEXTUREGROUP_Project01.DisplayName=UI With MIPs
覆写UI子系统
需要使用Lyra项目的插件进行覆写
GameUIManagerSubsystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* This manager is intended to be replaced by whatever your game needs to
* actually create, so this class is abstract to prevent it from being created.
*
* 这个管理器旨在根据您的游戏实际需求进行替换,因此这个类是抽象类,以防止其被创建。
*
* If you just need the basic functionality you will start by sublcassing this
* subsystem in your own game.
*
* 如果您只需要基本功能,那么您只需在自己的游戏中继承此子系统即可。
*
*/
UCLASS(MinimalAPI, Abstract, config = Game)
class UGameUIManagerSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UGameUIManagerSubsystem() { }
// 创建策略
UE_API virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 回收策略
UE_API virtual void Deinitialize() override;
// 如果开发者没有自定义才需要创建该子系统
UE_API virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
// 获取当前策略
const UGameUIPolicy* GetCurrentUIPolicy() const { return CurrentPolicy; }
// 获取当前策略
UGameUIPolicy* GetCurrentUIPolicy() { return CurrentPolicy; }
// 转发
UE_API virtual void NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer);
// 转发
UE_API virtual void NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer);
// 转发
UE_API virtual void NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer);
protected:
// 切换策略
UE_API void SwitchToPolicy(UGameUIPolicy* InPolicy);
private:
// 持有
UPROPERTY(Transient)
TObjectPtr<UGameUIPolicy> CurrentPolicy = nullptr;
// 配置
UPROPERTY(config, EditAnywhere)
TSoftClassPtr<UGameUIPolicy> DefaultUIPolicyClass;
};
#undef UE_API
LyraUIManagerSubsystem(UI管理子系统)
LyraUIManagerSubsystem.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UCLASS()
class ULyraUIManagerSubsystem : public UGameUIManagerSubsystem
{
GENERATED_BODY()
public:
ULyraUIManagerSubsystem();
// 绑定Tick
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 移除Tick
virtual void Deinitialize() override;
private:
// Tick
bool Tick(float DeltaTime);
// 更改UI布局的可实现
void SyncRootLayoutVisibilityToShowHUD();
FTSTicker::FDelegateHandle TickHandle;
};
LyraUIManagerSubsystem.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void ULyraUIManagerSubsystem::SyncRootLayoutVisibilityToShowHUD()
{
// Get the current UI policy to determine layout rules
// 获取当前UI策略来确定布局规则
if (const UGameUIPolicy* Policy = GetCurrentUIPolicy())
{
// Check all local players' HUD visibility states
// 检查所有本地玩家的HUD可见性状态
for (const ULocalPlayer* LocalPlayer : GetGameInstance()->GetLocalPlayers())
{
bool bShouldShowUI = true;
// Check if the player controller's HUD is set to show
// 检查玩家控制器的HUD是否设置为显示
if (const APlayerController* PC = LocalPlayer->GetPlayerController(GetWorld()))
{
const AHUD* HUD = PC->GetHUD();
// Hide UI if HUD is explicitly set to not show
// 如果HUD明确设置为不显示,则隐藏UI
if (HUD && !HUD->bShowHUD)
{
bShouldShowUI = false;
}
}
// Apply visibility state to the root layout
// 将可见性状态应用到根布局
if (UPrimaryGameLayout* RootLayout = Policy->GetRootLayout(CastChecked<UCommonLocalPlayer>(LocalPlayer)))
{
const ESlateVisibility DesiredVisibility = bShouldShowUI ? ESlateVisibility::SelfHitTestInvisible : ESlateVisibility::Collapsed;
// Only update if visibility has changed to avoid unnecessary operations
// 仅在可见性发生变化时更新,避免不必要的操作
if (DesiredVisibility != RootLayout->GetVisibility())
{
RootLayout->SetVisibility(DesiredVisibility);
}
}
}
}
}
LyraUIMessaging(UI消息子系统)
LyraUIMessaging.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// UI Messaging subsystem for handling game dialogs and user notifications
// UI消息子系统,用于处理游戏对话框和用户通知
UCLASS()
class ULyraUIMessaging : public UCommonMessagingSubsystem
{
GENERATED_BODY()
public:
ULyraUIMessaging() { }
// Initialize the messaging subsystem and load dialog classes
// 初始化消息子系统并加载对话框类
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// Show a confirmation dialog to the user
// 向用户显示确认对话框
virtual void ShowConfirmation(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback = FCommonMessagingResultDelegate()) override;
// Show an error dialog to the user
// 向用户显示错误对话框
virtual void ShowError(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback = FCommonMessagingResultDelegate()) override;
private:
// Loaded confirmation dialog class for instantiation
// 已加载的确认对话框类,用于实例化
UPROPERTY()
TSubclassOf<UCommonGameDialog> ConfirmationDialogClassPtr;
// Loaded error dialog class for instantiation
// 已加载的错误对话框类,用于实例化
UPROPERTY()
TSubclassOf<UCommonGameDialog> ErrorDialogClassPtr;
// Config reference to confirmation dialog class (soft reference for async loading)
// 配置中对确认对话框类的引用(软引用,用于异步加载)
UPROPERTY(config)
TSoftClassPtr<UCommonGameDialog> ConfirmationDialogClass;
// Config reference to error dialog class (soft reference for async loading)
// 配置中对错误对话框类的引用(软引用,用于异步加载)
UPROPERTY(config)
TSoftClassPtr<UCommonGameDialog> ErrorDialogClass;
};
LyraUIMessaging.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_UI_LAYER_MODAL, "UI.Layer.Modal");
void ULyraUIMessaging::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// Synchronously load the dialog classes from their soft references
// 从软引用同步加载对话框类
ConfirmationDialogClassPtr = ConfirmationDialogClass.LoadSynchronous();
ErrorDialogClassPtr = ErrorDialogClass.LoadSynchronous();
}
void ULyraUIMessaging::ShowConfirmation(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback)
{
// Get the local player to find the root UI layout
// 获取本地玩家以查找根UI布局
if (UCommonLocalPlayer* LocalPlayer = GetLocalPlayer<UCommonLocalPlayer>())
{
// Access the root layout where dialogs will be displayed
// 访问将显示对话框的根布局
if (UPrimaryGameLayout* RootLayout = LocalPlayer->GetRootUILayout())
{
// Push the confirmation dialog to the modal layer with configuration
// 将确认对话框推送到模态层并进行配置
RootLayout->PushWidgetToLayerStack<UCommonGameDialog>(TAG_UI_LAYER_MODAL, ConfirmationDialogClassPtr, [DialogDescriptor, ResultCallback](UCommonGameDialog& Dialog) {
Dialog.SetupDialog(DialogDescriptor, ResultCallback);
});
}
}
}
void ULyraUIMessaging::ShowError(UCommonGameDialogDescriptor* DialogDescriptor, FCommonMessagingResultDelegate ResultCallback)
{
// Get the local player to find the root UI layout
// 获取本地玩家以查找根UI布局
if (UCommonLocalPlayer* LocalPlayer = GetLocalPlayer<UCommonLocalPlayer>())
{
// Access the root layout where dialogs will be displayed
// 访问将显示对话框的根布局
if (UPrimaryGameLayout* RootLayout = LocalPlayer->GetRootUILayout())
{
// Push the error dialog to the modal layer with configuration
// 将错误对话框推送到模态层并进行配置
RootLayout->PushWidgetToLayerStack<UCommonGameDialog>(TAG_UI_LAYER_MODAL, ErrorDialogClassPtr, [DialogDescriptor, ResultCallback](UCommonGameDialog& Dialog) {
Dialog.SetupDialog(DialogDescriptor, ResultCallback);
});
}
}
}
CommonLoadingScreen_Plugins(通用加载界面)
流程介绍
1.定义一个接口,让需要使用到这个功能的实例继承自它. 2.定义了一个开发者设置可以用于在项目中设置,将其变量暴露到命令行中,可以快捷调试 3.定义了一个管理类,负责监听所有接口实例的生命周期,并通过该类分发具体的功能 4.暴露了蓝图静态函数,异步蓝图节点等方式提供便捷地使用.
Interface_加载进程接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** Interface for things that might cause loading to happen which requires a loading screen to be displayed */
/** 用于表示那些可能会触发加载操作(且该操作需要显示加载界面)的接口 */
UINTERFACE(MinimalAPI, BlueprintType)
class ULoadingProcessInterface : public UInterface
{
GENERATED_BODY()
};
class ILoadingProcessInterface
{
GENERATED_BODY()
public:
// Checks to see if this object implements the interface, and if so asks whether or not we should
// be currently showing a loading screen
// 检查该对象是否实现了该接口,如果实现了则询问是否当前应显示加载界面
static UE_API bool ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason);
virtual bool ShouldShowLoadingScreen(FString& OutReason) const
{
return false;
}
};
LoadingScreenManager(界面加载管理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* Handles showing/hiding the loading screen
* 控制显示/隐藏加载界面
*/
UCLASS(MinimalAPI)
class ULoadingScreenManager : public UGameInstanceSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public:
//~USubsystem interface
// 绑定地图的变动
UE_API virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 移除加载屏幕的功能
UE_API virtual void Deinitialize() override;
// 客户端才创建
UE_API virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
//~End of USubsystem interface
//~FTickableObjectBase interface
// 每帧检测是否要显示加载屏幕
UE_API virtual void Tick(float DeltaTime) override;
// 决定Tick的开关类型
UE_API virtual ETickableTickType GetTickableTickType() const override;
// 补充是否需要显示加载屏幕
UE_API virtual bool IsTickable() const override;
// ID
UE_API virtual TStatId GetStatId() const override;
// 应用的世界
UE_API virtual UWorld* GetTickableGameObjectWorld() const override;
//~End of FTickableObjectBase interface
// 暴露给蓝图询问
UFUNCTION(BlueprintCallable, Category=LoadingScreen)
FString GetDebugReasonForShowingOrHidingLoadingScreen() const
{
return DebugReasonForShowingOrHidingLoadingScreen;
}
/** Returns True when the loading screen is currently being shown */
/** 当加载界面正在显示时返回 True */
bool GetLoadingScreenDisplayStatus() const
{
return bCurrentlyShowingLoadingScreen;
}
/** Called when the loading screen visibility changes */
/** 当加载界面的可见性发生变化时调用 */
DECLARE_MULTICAST_DELEGATE_OneParam(FOnLoadingScreenVisibilityChangedDelegate, bool);
FORCEINLINE FOnLoadingScreenVisibilityChangedDelegate& OnLoadingScreenVisibilityChangedDelegate() { return LoadingScreenVisibilityChanged; }
// 由具体的接口实例进行生命周期的注册
UE_API void RegisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
UE_API void UnregisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
private:
// 触发更新
UE_API void HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName);
// 触发更新
UE_API void HandlePostLoadMap(UWorld* World);
/** Determines if we should show or hide the loading screen. Called every frame. */
/** 用于确定是否应显示或隐藏加载界面。每帧都会调用此函数。*/
UE_API void UpdateLoadingScreen();
/** Returns true if we need to be showing the loading screen. */
/** 若需要显示加载界面,则返回 true 。*/
UE_API bool CheckForAnyNeedToShowLoadingScreen();
/** Returns true if we want to be showing the loading screen (if we need to or are artificially forcing it on for other reasons). */
/** 若我们希望显示加载界面,则返回 true(这意味着我们需要显示该界面,或者是因为其他人为原因而强制显示)。*/
UE_API bool ShouldShowLoadingScreen();
/** Returns true if we are in the initial loading flow before this screen should be used */
/** 如果我们正处于初始加载流程中,且在此屏幕投入使用之前,返回 true */
UE_API bool IsShowingInitialLoadingScreen() const;
/** Shows the loading screen. Sets up the loading screen widget on the viewport */
/** 显示加载界面。在视口上设置加载界面组件 */
UE_API void ShowLoadingScreen();
/** Hides the loading screen. The loading screen widget will be destroyed */
/** 隐藏加载界面。加载界面的组件将会被销毁 */
UE_API void HideLoadingScreen();
/** Removes the widget from the viewport */
/** 将该控件从视口中移除 */
UE_API void RemoveWidgetFromViewport();
/** Prevents input from being used in-game while the loading screen is visible */
/** 在加载界面显示期间,防止输入内容被用于游戏之中 */
UE_API void StartBlockingInput();
/** Resumes in-game input, if blocking */
/** 若存在阻塞情况,则恢复游戏中的输入操作 */
UE_API void StopBlockingInput();
// 变更由于切换加载屏幕的性能影响
UE_API void ChangePerformanceSettings(bool bEnabingLoadingScreen);
private:
/** Delegate broadcast when the loading screen visibility changes */
/** 当加载界面的可见性发生变化时,执行广播操作 */
FOnLoadingScreenVisibilityChangedDelegate LoadingScreenVisibilityChanged;
/** A reference to the loading screen widget we are displaying (if any) */
/** 指向我们正在显示的加载屏幕组件的引用(如果有的话) */
TSharedPtr<SWidget> LoadingScreenWidget;
/** Input processor to eat all input while the loading screen is shown */
/** 用于在加载界面显示期间接收所有输入的输入处理器 */
TSharedPtr<IInputProcessor> InputPreProcessor;
/** External loading processors, components maybe actors that delay the loading. */
/** 外部加载处理器、组件可能包括那些会延迟加载过程的执行者。*/
TArray<TWeakInterfacePtr<ILoadingProcessInterface>> ExternalLoadingProcessors;
/** The reason why the loading screen is up (or not) */
/** 加载界面是否显示(或未显示)的原因 */
FString DebugReasonForShowingOrHidingLoadingScreen;
/** The time when we started showing the loading screen */
/** 我们开始显示加载界面的时间 */
double TimeLoadingScreenShown = 0.0;
/** The time the loading screen most recently wanted to be dismissed (might still be up due to a min display duration requirement) **/
/** 加载界面最后一次想要被关闭的时间(由于最小显示时长要求,该界面可能仍处于显示状态) **/
double TimeLoadingScreenLastDismissed = -1.0;
/** The time until the next log for why the loading screen is still up */
/** 下一次记录加载屏幕仍处于显示状态的原因所需的时间 */
double TimeUntilNextLogHeartbeatSeconds = 0.0;
/** True when we are between PreLoadMap and PostLoadMap */
/** 当我们处于“预加载地图”与“后加载地图”之间时为真 */
bool bCurrentlyInLoadMap = false;
/** True when the loading screen is currently being shown */
/** 当加载界面正在显示时为真 */
bool bCurrentlyShowingLoadingScreen = false;
};
输入屏蔽
头文件(.h)
1
2
3
4
5
6
7
/** Prevents input from being used in-game while the loading screen is visible */
/** 在加载界面显示期间,防止输入内容被用于游戏之中 */
UE_API void StartBlockingInput();
/** Resumes in-game input, if blocking */
/** 若存在阻塞情况,则恢复游戏中的输入操作 */
UE_API void StopBlockingInput();
源文件(.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Input processor to throw in when loading screen is shown
// This will capture any inputs, so active menus under the loading screen will not interact
// 在加载界面显示时使用的输入处理器
// 此处理器将捕获所有输入操作,因此加载界面下的活动菜单将不会与之交互
class FLoadingScreenInputPreProcessor : public IInputProcessor
{
public:
FLoadingScreenInputPreProcessor() { }
virtual ~FLoadingScreenInputPreProcessor() { }
bool CanEatInput() const
{
return !GIsEditor;
}
//~IInputProcess interface
virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override { }
virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); }
virtual bool HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); }
virtual bool HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) override { return CanEatInput(); }
virtual bool HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
virtual bool HandleMouseButtonUpEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
virtual bool HandleMouseButtonDoubleClickEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) override { return CanEatInput(); }
virtual bool HandleMotionDetectedEvent(FSlateApplication& SlateApp, const FMotionEvent& MotionEvent) override { return CanEatInput(); }
//~End of IInputProcess interface
};
更新加载屏幕可视性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void ULoadingScreenManager::UpdateLoadingScreen()
{
bool bLogLoadingScreenStatus = LoadingScreenCVars::LogLoadingScreenReasonEveryFrame;
if (ShouldShowLoadingScreen())
{
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
// If we don't make it to the specified checkpoint in the given time will trigger the hang detector so we can better determine where progress stalled.
// 如果我们未能在规定时间内到达指定的检查点,就会触发挂起检测机制,这样我们就能更清楚地了解进度停滞的具体位置。
FThreadHeartBeat::Get().MonitorCheckpointStart(GetFName(), Settings->LoadingScreenHeartbeatHangDuration);
ShowLoadingScreen();
if ((Settings->LogLoadingScreenHeartbeatInterval > 0.0f) && (TimeUntilNextLogHeartbeatSeconds <= 0.0))
{
bLogLoadingScreenStatus = true;
TimeUntilNextLogHeartbeatSeconds = Settings->LogLoadingScreenHeartbeatInterval;
}
}
else
{
HideLoadingScreen();
/* 当检查点结束时,由线程调用 */
FThreadHeartBeat::Get().MonitorCheckpointEnd(GetFName());
}
if (bLogLoadingScreenStatus)
{
UE_LOG(LogLoadingScreen, Log, TEXT("Loading screen showing: %d. Reason: %s"), bCurrentlyShowingLoadingScreen ? 1 : 0, *DebugReasonForShowingOrHidingLoadingScreen);
}
}
当下计算可视性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
bool ULoadingScreenManager::CheckForAnyNeedToShowLoadingScreen()
{
// Start out with 'unknown' reason in case someone forgets to put a reason when changing this in the future.
// 一开始将原因设为“未知”,以防日后有人在修改此项内容时忘记填写原因。
DebugReasonForShowingOrHidingLoadingScreen = TEXT("Reason for Showing/Hiding LoadingScreen is unknown!");
const UGameInstance* LocalGameInstance = GetGameInstance();
// 是否强制显示
if (LoadingScreenCVars::ForceLoadingScreenVisible)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommonLoadingScreen.AlwaysShow is true"));
return true;
}
const FWorldContext* Context = LocalGameInstance->GetWorldContext();
if (Context == nullptr)
{
// We don't have a world context right now... better show a loading screen
// 目前我们还没有全球性的背景信息……最好显示一个加载界面
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("The game instance has a null WorldContext"));
return true;
}
UWorld* World = Context->World();
// 无世界
if (World == nullptr)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have no world (FWorldContext's World() is null)"));
return true;
}
AGameStateBase* GameState = World->GetGameState<AGameStateBase>();
// 无GameState
if (GameState == nullptr)
{
// The game state has not yet replicated.
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("GameState hasn't yet replicated (it's null)"));
return true;
}
// 加载地图中
if (bCurrentlyInLoadMap)
{
// Show a loading screen if we are in LoadMap
// 如果处于“加载地图”状态,则显示加载界面
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("bCurrentlyInLoadMap is true"));
return true;
}
/** 用于处理待连接客户端的跳转 URL */
if (!Context->TravelURL.IsEmpty())
{
// Show a loading screen when pending travel
// 当有待处理的行程时显示加载界面
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have pending travel (the TravelURL is not empty)"));
return true;
}
if (Context->PendingNetGame != nullptr)
{
// Connecting to another server
// 连接到另一台服务器
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are connecting to another server (PendingNetGame != nullptr)"));
return true;
}
// 世界还没开始
if (!World->HasBegunPlay())
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("World hasn't begun play"));
return true;
}
if (World->IsInSeamlessTravel())
{
// Show a loading screen during seamless travel
// 在无缝切换过程中显示加载界面
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are in seamless travel"));
return true;
}
// Ask the game state if it needs a loading screen
// 调用游戏状态对象,询问其是否需要加载界面
if (ILoadingProcessInterface::ShouldShowLoadingScreen(GameState, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
// Ask any game state components if they need a loading screen
// 要求任何游戏状态组件告知自身是否需要加载界面
for (UActorComponent* TestComponent : GameState->GetComponents())
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
// Ask any of the external loading processors that may have been registered. These might be actors or components
// that were registered by game code to tell us to keep the loading screen up while perhaps something finishes
// streaming in.
// 联系任何可能已注册的外部加载处理器。这些处理器可能是由游戏代码注册的演员或组件,它们会告知我们应保持加载界面处于显示状态,以便在某些内容完成加载传输时能够显示出来。
for (const TWeakInterfacePtr<ILoadingProcessInterface>& Processor : ExternalLoadingProcessors)
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(Processor.GetObject(), /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
// Check each local player
// 检查每个本地玩家
bool bFoundAnyLocalPC = false;
bool bMissingAnyLocalPC = false;
for (ULocalPlayer* LP : LocalGameInstance->GetLocalPlayers())
{
if (LP != nullptr)
{
if (APlayerController* PC = LP->PlayerController)
{
bFoundAnyLocalPC = true;
// Ask the PC itself if it needs a loading screen
// 要求电脑自身确认是否需要加载画面
if (ILoadingProcessInterface::ShouldShowLoadingScreen(PC, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
// Ask any PC components if they need a loading screen
// 询问任何电脑组件是否需要加载画面
for (UActorComponent* TestComponent : PC->GetComponents())
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
}
else
{
bMissingAnyLocalPC = true;
}
}
}
UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
const bool bIsInSplitscreen = GameViewportClient->GetCurrentSplitscreenConfiguration() != ESplitScreenType::None;
// In splitscreen we need all player controllers to be present
// 在分屏模式下,我们需要确保所有玩家控制器都处于开启状态
if (bIsInSplitscreen && bMissingAnyLocalPC)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("At least one missing local player controller in splitscreen"));
return true;
}
// And in non-splitscreen we need at least one player controller to be present
// 在非分屏模式下,我们至少需要有一个玩家控制器在场。
if (!bIsInSplitscreen && !bFoundAnyLocalPC)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("Need at least one local player controller"));
return true;
}
// Victory! The loading screen can go away now
// 胜利!加载画面现在可以消失了
DebugReasonForShowingOrHidingLoadingScreen = TEXT("(nothing wants to show it anymore)");
return false;
}
延时补充可视性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
// Check debugging commands that force the state one way or another
// 检查那些能够强制改变状态的调试命令
#if !UE_BUILD_SHIPPING
// 命令行无加载界面
static bool bCmdLineNoLoadingScreen = FParse::Param(FCommandLine::Get(), TEXT("NoLoadingScreen"));
if (bCmdLineNoLoadingScreen)
{
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommandLine has 'NoLoadingScreen'"));
return false;
}
#endif
// Can't show a loading screen if there's no game viewport
// 若没有游戏视图区域,则无法显示加载屏幕
UGameInstance* LocalGameInstance = GetGameInstance();
if (LocalGameInstance->GetGameViewportClient() == nullptr)
{
return false;
}
// Check for a need to show the loading screen
// 检查是否需要显示加载界面
const bool bNeedToShowLoadingScreen = CheckForAnyNeedToShowLoadingScreen();
// Keep the loading screen up a bit longer if desired
// 如果需要的话,可以让加载界面多显示一段时间
bool bWantToForceShowLoadingScreen = false;
if (bNeedToShowLoadingScreen)
{
// Still need to show it
// 仍需展示它
TimeLoadingScreenLastDismissed = -1.0;
}
else
{
// Don't *need* to show the screen anymore, but might still want to for a bit
// 无需再显示该屏幕了,但可能仍需要展示一小段时间。
const double CurrentTime = FPlatformTime::Seconds();
const bool bCanHoldLoadingScreen = (!GIsEditor || Settings->HoldLoadingScreenAdditionalSecsEvenInEditor);
const double HoldLoadingScreenAdditionalSecs = bCanHoldLoadingScreen ? LoadingScreenCVars::HoldLoadingScreenAdditionalSecs : 0.0;
if (TimeLoadingScreenLastDismissed < 0.0)
{
TimeLoadingScreenLastDismissed = CurrentTime;
}
const double TimeSinceScreenDismissed = CurrentTime - TimeLoadingScreenLastDismissed;
// hold for an extra X seconds, to cover up streaming
// 延长等待 X 秒,以弥补数据传输的延迟
if ((HoldLoadingScreenAdditionalSecs > 0.0) && (TimeSinceScreenDismissed < HoldLoadingScreenAdditionalSecs))
{
// Make sure we're rendering the world at this point, so that textures will actually stream in
//@TODO: If bNeedToShowLoadingScreen bounces back true during this window, we won't turn this off again...
// 确保此时我们正在渲染整个世界,这样纹理才能真正加载进来
//@待办事项:如果在这一窗口期间 bNeedToShowLoadingScreen 的值反向变为 true,我们就不会再次将其关闭……
UGameViewportClient* GameViewportClient = GetGameInstance()->GetGameViewportClient();
/** 是否设置为禁用世界渲染 */
// 开始渲染3D画面
GameViewportClient->bDisableWorldRendering = false;
DebugReasonForShowingOrHidingLoadingScreen = FString::Printf(TEXT("Keeping loading screen up for an additional %.2f seconds to allow texture streaming"), HoldLoadingScreenAdditionalSecs);
bWantToForceShowLoadingScreen = true;
}
}
return bNeedToShowLoadingScreen || bWantToForceShowLoadingScreen;
调用执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void ULoadingScreenManager::Initialize(FSubsystemCollectionBase& Collection)
{
/** 在加载地图操作之初发送 */
FCoreUObjectDelegates::PreLoadMapWithContext.AddUObject(this, &ThisClass::HandlePreLoadMap);
/** 在加载地图操作完成后发送 */
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &ThisClass::HandlePostLoadMap);
const UGameInstance* LocalGameInstance = GetGameInstance();
check(LocalGameInstance);
}
void ULoadingScreenManager::HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName)
{
if (WorldContext.OwningGameInstance == GetGameInstance())
{
bCurrentlyInLoadMap = true;
// Update the loading screen immediately if the engine is initialized
// 若引擎已初始化,则立即更新加载界面
if (GEngine->IsInitialized())
{
UpdateLoadingScreen();
}
}
}
1
2
3
4
5
6
void ULoadingScreenManager::Tick(float DeltaTime)
{
UpdateLoadingScreen();
TimeUntilNextLogHeartbeatSeconds = FMath::Max(TimeUntilNextLogHeartbeatSeconds - DeltaTime, 0.0);
}
实例的生命周期
注意此处只是用于手动的.实际上会自动访问GameState及其组件是否需要这个功能!
1
2
3
// 由具体的接口实例进行生命周期的注册
UE_API void RegisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
UE_API void UnregisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*static*/ ULoadingProcessTask* ULoadingProcessTask::CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr;
ULoadingScreenManager* LoadingScreenManager = GameInstance ? GameInstance->GetSubsystem<ULoadingScreenManager>() : nullptr;
if (LoadingScreenManager)
{
ULoadingProcessTask* NewLoadingTask = NewObject<ULoadingProcessTask>(LoadingScreenManager);
NewLoadingTask->SetShowLoadingScreenReason(ShowLoadingScreenReason);
LoadingScreenManager->RegisterLoadingProcessor(NewLoadingTask);
return NewLoadingTask;
}
return nullptr;
}
外部闻讯
代理
1
2
3
4
private:
/** Delegate broadcast when the loading screen visibility changes */
/** 当加载界面的可见性发生变化时,执行广播操作 */
FOnLoadingScreenVisibilityChangedDelegate LoadingScreenVisibilityChanged;
访问
1
2
3
4
5
6
// 暴露给蓝图询问
UFUNCTION(BlueprintCallable, Category=LoadingScreen)
FString GetDebugReasonForShowingOrHidingLoadingScreen() const
{
return DebugReasonForShowingOrHidingLoadingScreen;
}
CommonLoadingScreenSettings(通用界面加载设置)
具体使用是在编辑器中ProjectSettings->Gane->Common Loading Screen进行配置使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* Settings for a loading screen system.
* 加载屏幕系统的设置。
*/
UCLASS(config=Game, defaultconfig, meta=(DisplayName="Common Loading Screen"))
class UCommonLoadingScreenSettings : public UDeveloperSettingsBackedByCVars
{
GENERATED_BODY()
public:
UCommonLoadingScreenSettings();
public:
// The widget to load for the loading screen.
// 用于加载屏幕的加载组件。
UPROPERTY(config, EditAnywhere, Category=Display, meta=(MetaClass="/Script/UMG.UserWidget"))
FSoftClassPath LoadingScreenWidget;
// The z-order of the loading screen widget in the viewport stack
// 视图堆栈中加载屏幕组件的层级顺序
UPROPERTY(config, EditAnywhere, Category=Display)
int32 LoadingScreenZOrder = 10000;
// How long to hold the loading screen up after other loading finishes (in seconds) to
// try to give texture streaming a chance to avoid blurriness
//
// Note: This is not normally applied in the editor for iteration time, but can be
// enabled via HoldLoadingScreenAdditionalSecsEvenInEditor
// 其他加载完成之后,要将加载界面保持显示多长时间(以秒为单位),以期让纹理流加载有机会避免出现模糊现象//
// 注意:通常情况下,此功能不适用于编辑器中的迭代时间,但可以通过“HoldLoadingScreenAdditionalSecsEvenInEditor”选项来启用。
UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s, ConsoleVariable="CommonLoadingScreen.HoldLoadingScreenAdditionalSecs"))
float HoldLoadingScreenAdditionalSecs = 2.0f;
// The interval in seconds beyond which the loading screen is considered permanently hung (if non-zero).
// 超过此时间间隔(以秒为单位)后,加载界面将被视为永久性卡住(若该值非零)。
UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s))
float LoadingScreenHeartbeatHangDuration = 0.0f;
// The interval in seconds between each log of what is keeping a loading screen up (if non-zero).
// 每次记录加载屏幕保持状态变化的时间间隔(若不为零)。单位:秒。
UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s))
float LogLoadingScreenHeartbeatInterval = 5.0f;
// When true, the reason the loading screen is shown or hidden will be printed to the log every frame.
// 若为真,则每次渲染时都会将加载界面显示或隐藏的原因记录到日志中。
UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.LogLoadingScreenReasonEveryFrame"))
bool LogLoadingScreenReasonEveryFrame = 0;
// Force the loading screen to be displayed (useful for debugging)
// 强制显示加载界面(用于调试之用)
UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.AlwaysShow"))
bool ForceLoadingScreenVisible = false;
// Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor
// (useful when iterating on loading screens)
// 我们是否应该在编辑器中也使用额外的“保持加载界面显示时间(额外秒数)”这一设置呢?
// (在对加载界面进行改进时,此设置很有用)
UPROPERTY(Transient, EditAnywhere, Category=Debugging)
bool HoldLoadingScreenAdditionalSecsEvenInEditor = false;
// Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor
// (useful when iterating on loading screens)
// 我们是否应该在编辑器中也使用额外的“保持加载界面显示时间(额外秒数)”这一设置呢?
// (在对加载界面进行改进时,此设置很有用)
UPROPERTY(config, EditAnywhere, Category=Configuration)
bool ForceTickLoadingScreenEvenInEditor = true;
};
LoadingProcessTask(加载进程任务)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建一个接口实例,用来临时显示加载屏幕.
// 注意一定要持有这个对象,防止GC
// 和异步蓝图节点的写法很像.
UCLASS(MinimalAPI, BlueprintType)
class ULoadingProcessTask : public UObject, public ILoadingProcessInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, meta=(WorldContext = "WorldContextObject"))
static UE_API ULoadingProcessTask* CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason);
public:
ULoadingProcessTask() { }
UFUNCTION(BlueprintCallable)
UE_API void Unregister();
UFUNCTION(BlueprintCallable)
UE_API void SetShowLoadingScreenReason(const FString& InReason);
UE_API virtual bool ShouldShowLoadingScreen(FString& OutReason) const override;
FString Reason;
};
蓝图调用
此处为B_LoadRandomLobbyBackground类的蓝图 
代码实现
用来在前端界面流送背景关卡的资产定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Developer settings / editor cheats
* 开发者设置 / 编辑器作弊功能
*
* 用来加载背景的资产,这个资产需要进行扫描
*/
UCLASS(config=EditorPerProjectUserSettings, MinimalAPI)
class ULyraLobbyBackground : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSoftObjectPtr<UWorld> BackgroundLevel;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*static*/ ULoadingProcessTask* ULoadingProcessTask::CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr;
ULoadingScreenManager* LoadingScreenManager = GameInstance ? GameInstance->GetSubsystem<ULoadingScreenManager>() : nullptr;
if (LoadingScreenManager)
{
ULoadingProcessTask* NewLoadingTask = NewObject<ULoadingProcessTask>(LoadingScreenManager);
NewLoadingTask->SetShowLoadingScreenReason(ShowLoadingScreenReason);
LoadingScreenManager->RegisterLoadingProcessor(NewLoadingTask);
return NewLoadingTask;
}
return nullptr;
}
void ULoadingProcessTask::Unregister()
{
ULoadingScreenManager* LoadingScreenManager = Cast<ULoadingScreenManager>(GetOuter());
LoadingScreenManager->UnregisterLoadingProcessor(this);
}
void ULoadingProcessTask::SetShowLoadingScreenReason(const FString& InReason)
{
Reason = InReason;
}
bool ULoadingProcessTask::ShouldShowLoadingScreen(FString& OutReason) const
{
OutReason = Reason;
return true;
}
记得配置检索路径! LyraLobbyBackground在ShooterMaps.GameFeatureData中! 不配置话的就找不到这个资产!!!!
1
2
[/Script/Engine.AssetManagerSettings]
(PrimaryAssetType="LyraLobbyBackground",AssetBaseClass="/Script/LyraGame.LyraLobbyBackground",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="Items/Backgrounds")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=AlwaysCook))
CommonStartupLoadingScreen(通用启动加载界面)
SCommonPreLoadingScreenWidget
头文件(.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FReferenceCollector;
class SCommonPreLoadingScreenWidget : public SCompoundWidget, public FGCObject
{
public:
SLATE_BEGIN_ARGS(SCommonPreLoadingScreenWidget) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
//~ Begin FGCObject interface
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
virtual FString GetReferencerName() const override;
//~ End FGCObject interface
private:
};
源文件(.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void SCommonPreLoadingScreenWidget::Construct(const FArguments& InArgs)
{
// Create the widget content
// 创建控件内容
ChildSlot
[
SNew(SBorder) // Create a new border widget
// 创建一个新的边框控件
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush")) // Set border image to white brush
// 设置边框图像为白色画刷
.BorderBackgroundColor(FLinearColor::Black) // Set background color to black
// 设置背景颜色为黑色
.Padding(0) // Set padding to zero
// 设置内边距为零
];
}
void SCommonPreLoadingScreenWidget::AddReferencedObjects(FReferenceCollector& Collector)
{
//WidgetAssets.AddReferencedObjects(Collector);
}
FString SCommonPreLoadingScreenWidget::GetReferencerName() const
{
return TEXT("SCommonPreLoadingScreenWidget");
}
Slate界面实例化位置
头文件(.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//基类
//用于处理预加载屏幕相关逻辑的基类实现,负责控制/更新预加载屏幕的用户界面
//设计为可被特定游戏插件重写,该插件会调用FPreloadScreeManage::RegisterPreLoadScreen方法,以便预加载屏幕管理器能够正确调用相关函数
class FCommonPreLoadScreen : public FPreLoadScreenBase
{
public:
/*** IPreLoadScreen Implementation ***/
virtual void Init() override;
virtual EPreLoadScreenTypes GetPreLoadScreenType() const override { return EPreLoadScreenTypes::EngineLoadingScreen; }
virtual TSharedPtr<SWidget> GetWidget() override { return EngineLoadingWidget; }
private:
TSharedPtr<SWidget> EngineLoadingWidget;
};
源文件(.cpp)
1
2
3
4
5
6
7
void FCommonPreLoadScreen::Init()
{
if (!GIsEditor && FApp::CanEverRender())
{
EngineLoadingWidget = SNew(SCommonPreLoadingScreenWidget);
}
}
具体实现流程
1
2
3
4
5
6
7
void FCommonPreLoadScreen::Init()
{
if (!GIsEditor && FApp::CanEverRender())
{
EngineLoadingWidget = SNew(SCommonPreLoadingScreenWidget);
}
}
项目报错情况
项目Experience资产未正常配置
请确保资产管理器中对于Experience的检索定义已确认.否则会出现如下崩溃:
| 日志详细级别 | 打印在控制台中 | 打印在编辑器日志中 | 文本颜色 | 其他信息 |
|---|---|---|---|---|
| 日志(Log) | ❌ 否 | ✅ 是 | ⚫ 灰色 | ❌ 不适用 |
| 错误(Error) | ✅ 是 | ✅ 是 | 🔴 红色 | 体验资源加载失败,Asset ID 解析错误 |
| 日志(Log) | ❌ 否 | ✅ 是 | ⚫ 灰色 | ❌ 不适用 |
| 警告(Warning) | ✅ 是 | ✅ 是 | 🟡 黄色 | 脚本堆栈为空 |
| 错误(Error) | ✅ 是 | ✅ 是 | 🔴 红色 | 断言失败:AssetClass,文件 LyraExperienceManagerComponent.cpp:91 |
| 日志(Log) | ❌ 否 | ✅ 是 | ⚫ 灰色 | Windows 系统错误:操作成功完成 (0) |
未修改配置导致崩溃
DefaultGame.ini
1
2
3
[/Script/LyraGame.LyraUIManagerSubsystem]
; 默认的UI管理策略
DefaultUIPolicyClass=/Game/UI/B_LyraUIPolicy.B_LyraUIPolicy_C
如果没有配置的话,会在这里崩溃!因为空指针了!
1
2
3
4
5
6
7
void UGameUIManagerSubsystem::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer)
{
if (ensure(LocalPlayer) && CurrentPolicy)
{
CurrentPolicy->NotifyPlayerAdded(LocalPlayer);
}
}
未显示加载界面
DefaultGame.ini
1
2
3
4
5
[/Script/CommonLoadingScreen.CommonLoadingScreenSettings]
; 默认的加载界面
LoadingScreenWidget=/Game/UI/Foundation/LoadingScreen/W_LoadingScreen_Host.W_LoadingScreen_Host_C
; 强制Tick加载界面即使是在编辑器
ForceTickLoadingScreenEvenInEditor=False
1
2
PIE: Error: CreateWidget called with a null class.
LogLoadingScreen: Error: Failed to load the loading screen widget , falling back to placeholder.
需在GameMode中使用CommonPlayerController类
本文由作者按照 CC BY 4.0 进行授权



