ActionRPG项目中增加了一个自定义模块:ActionRPGLoadingScreenModule。在这个模块中只有ActionRPGLoadingScreen.h/cpp,实现的功能有:使用Slate绘制加载界面、显示和隐藏加载界面。
当然,我们也可以使用单独一个loading map来实现过渡。
ActionRPGLoadingScreen.h
除了项目自动生成的module(ActionRPGModule)之外,所有自定义的模块都要定义一个继承自IModuleInterface
类的新类,然后使用IMPLEMENT_MODULE/IMPLEMENT_GAME_MODULE
声明。之后,引擎会在加载模块时自动生成对应类的实例。
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ModuleInterface.h"
#include "Modules/ModuleManager.h"
/** Module interface for this game's loading screens */
/** 游戏加载界面的模块接口 **/
class IActionRPGLoadingScreenModule : public IModuleInterface
{
public:
/** Loads the module so it can be turned on */
/** 加载模块,并获得实例,不会重复加载 **/
static inline IActionRPGLoadingScreenModule& Get()
{
return FModuleManager::LoadModuleChecked("ActionRPGLoadingScreen");
}
/** Kicks off the loading screen for in game loading (not startup) */
/** 在游戏中打开加载界面**/
virtual void StartInGameLoadingScreen(bool bPlayUntilStopped, float PlayTime) = 0;
/** Stops the loading screen */
/** 关闭加载界面 **/
virtual void StopInGameLoadingScreen() = 0;
};
注意定义的新类以“I”开头,我们称之为“接口”(与c#中的interface不是同一个东西)。
ActionRPGLoadingScreen.cpp
文件中有三个数据类型:
- FRPGLoadingScreenBrush : 自定义Slate笔刷
- SRPGLoadingScreen: 自定义Slate组合widget
- FActionRPGLoadingScreenModule : 模块接口实现类
引用
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "ActionRPGLoadingScreen.h"
#include "SlateBasics.h"
#include "SlateExtras.h"
#include "MoviePlayer.h"
#include "SThrobber.h"
// This module must be loaded "PreLoadingScreen" in the .uproject file, otherwise it will not hook in time!
// 这个模块的加载阶段必须是"PreLoadingScreen"
FRPGLoadingScreenBrush
struct FRPGLoadingScreenBrush : public FSlateDynamicImageBrush, public FGCObject
{
FRPGLoadingScreenBrush(const FName InTextureName, const FVector2D& InImageSize)
: FSlateDynamicImageBrush(InTextureName, InImageSize)
{
SetResourceObject(LoadObject(NULL, *InTextureName.ToString()));
}
virtual void AddReferencedObjects(FReferenceCollector& Collector)
{
if (UObject* CachedResourceObject = GetResourceObject())
{
Collector.AddReferencedObject(CachedResourceObject);
}
}
};
这个类涉及到UE的内存管理。Slate控件并不是继承自UObject
,类中的UObject
类型无法使用UPROPERTY()
标记,会被GC回收,如此处由LoadObject
方法加载的资源对象。为了解决这个问题,需要继承FGCObject
,并重写AddReferencedObjects
方法。
SRPGLoadingScreen
class SRPGLoadingScreen : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SRPGLoadingScreen) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
// Load version of the logo with text baked in, path is hardcoded because this loads very early in startup
static const FName LoadingScreenName(TEXT("/Game/UI/T_ActionRPG_TransparentLogo.T_ActionRPG_TransparentLogo"));
LoadingScreenBrush = MakeShareable(new FRPGLoadingScreenBrush(LoadingScreenName, FVector2D(1024, 256)));
FSlateBrush *BGBrush = new FSlateBrush();
BGBrush->TintColor = FLinearColor(0.034f, 0.034f, 0.034f, 1.0f);
ChildSlot
[
SNew(SOverlay)
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SBorder)
.BorderImage(BGBrush)
]
+SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(LoadingScreenBrush.Get())
]
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.VAlign(VAlign_Bottom)
.HAlign(HAlign_Right)
.Padding(FMargin(10.0f))
[
SNew(SThrobber)
.Visibility(this, &SRPGLoadingScreen::GetLoadIndicatorVisibility)
]
]
];
}
private:
/** Rather to show the ... indicator */
EVisibility GetLoadIndicatorVisibility() const
{
bool Vis = GetMoviePlayer()->IsLoadingFinished();
return GetMoviePlayer()->IsLoadingFinished() ? EVisibility::Collapsed : EVisibility::Visible;
}
/** Loading screen image brush */
TSharedPtr LoadingScreenBrush;
};
Slate组合Widget。Slate是一套UI编码系统,可以用来绘制UE Editor和游戏内的UI界面(游戏内UI推荐使用UMG)。
Slate官方文档
文档不怎么详细,使用Slate的更多例子可以在引擎的源代码,还有官方Shooter Game里面查看。
ChildSlot
中各个widget组合最终结果就是我们一开始看到的Loading界面。需要注意的是,Slate系统的对象内存是使用TSharedPtr
来管理的。
PS:用代码来写UI总是不够直观的,虽然它更易于操作数据和绑定事件。你可以先用UMG设计表现,然后再“翻译”成Slate。新版的UE已经支持Editor Utility Widget来拓展Editor UI。
FActionRPGLoadingScreenModule
class FActionRPGLoadingScreenModule : public IActionRPGLoadingScreenModule
{
public:
//加载module时调用
virtual void StartupModule() override
{
// Force load for cooker reference
// 这个强制load,还不确切明白其功能,应该是资源管理相关。我将其注释,也没出现问题。
LoadObject(nullptr, TEXT("/Game/UI/T_ActionRPG_TransparentLogo.T_ActionRPG_TransparentLogo") );
if (IsMoviePlayerEnabled())
{
CreateScreen();
}
}
//是否是游戏模块
virtual bool IsGameModule() const override
{
return true;
}
//在游戏中调用,显示加载界面
virtual void StartInGameLoadingScreen(bool bPlayUntilStopped, float PlayTime) override
{
FLoadingScreenAttributes LoadingScreen;
LoadingScreen.bAutoCompleteWhenLoadingCompletes = !bPlayUntilStopped;
LoadingScreen.bWaitForManualStop = bPlayUntilStopped;
LoadingScreen.bAllowEngineTick = bPlayUntilStopped;
LoadingScreen.MinimumLoadingScreenDisplayTime = PlayTime;
LoadingScreen.WidgetLoadingScreen = SNew(SRPGLoadingScreen);
GetMoviePlayer()->SetupLoadingScreen(LoadingScreen);
}
//在游戏中调用,隐藏加载界面
virtual void StopInGameLoadingScreen() override
{
GetMoviePlayer()->StopMovie();
}
//点开应用,最初进入游戏时调用,显示加载界面
virtual void CreateScreen()
{
FLoadingScreenAttributes LoadingScreen;
LoadingScreen.bAutoCompleteWhenLoadingCompletes = true;
LoadingScreen.MinimumLoadingScreenDisplayTime = 3.f;
LoadingScreen.WidgetLoadingScreen = SNew(SRPGLoadingScreen);
GetMoviePlayer()->SetupLoadingScreen(LoadingScreen);
}
};
//很重要,module都要有此声明,此处为游戏模块
IMPLEMENT_GAME_MODULE(FActionRPGLoadingScreenModule, ActionRPGLoadingScreen);
该类主要就是负责加载,显示,隐藏加载界面。有一个关键的类我们需要注意到:MoviePlayer
。这个类负责进入游戏时的加载界面,还有游戏中的过场动画等。
将Loading Screen用Slate编写并且放在单独的模块,而不是使用loading map,应该就是为了在进入游戏前(加载ActionRPG模块前)显示加载界面。
以上就是该模块的所有内容了。