《InsideUE4》GamePlay架构学习
WorldContext,GameInstance,Engine
前话
本次系列是关于知乎InsideUE系列的学习记录。原作链接如下:原文链接
WorldContext
前面对其他部分都花了大量时间来解释,那么World到底是什么。
首先游戏里不只有一个World,World有多种类型。比如不同的视图可以看作不同的World,编辑器也是一个World,运行后也是一个World。
简单来说,UE其实是一个平行宇宙世界观。
namespace EWorldType
{
enum Type
{
None, // An untyped world, in most cases this will be the vestigial worlds of streamed in sub-levels
Game, // The game world
Editor, // A world being edited in the editor
PIE, // A Play In Editor world
Preview, // A preview world for an editor tool
Inactive // An editor world that was loaded but not currently being edited in the level editor
};
UE_DEPRECATED(4.14, "EWorldType::Preview is deprecated. Please use either EWorldType::EditorPreview or EWorldType::GamePreview")
const EWorldType::Type Preview = EWorldType::EditorPreview;
}
而UE用来管理和跟踪这些World的工具就是WorldContext:
FWorldContext保存着ThisCurrentWorld来指向当前的World。而当需要从一个World切换到另一个World的时候(比如说当点击播放时,就是从Preview切换到PIE),FWorldContext就用来保存切换过程信息和目标World上下文信息。
一般就来说,对于独立运行的游戏,WorldContext只有唯一个。而对于编辑器模式,则是一个WorldContext给编辑器,一个WorldContext给PIE(Play In Editor)的World。一般UE会自己处理各个World之间的各种信息。
不仅如此,同时FWorldContext还保存着World里Level切换的上下文:
struct FWorldContext
{
[...]
TEnumAsByte<EWorldType::Type> WorldType
FSeamlessTravelHandler SeamlessTravelHandler;
FName ContextHandle;
/** URL to travel to for pending client connect */
FString TravelURL;
/** TravelType for pending client connects */
uint8 TravelType;
/** URL the last time we traveled */
UPROPERTY()
struct FURL LastURL;
/** last server we connected to (for "reconnect" command) */
UPROPERTY()
struct FURL LastRemoteURL;
}
这里的TravelURL和TravelType就是负责设定下一个Level的目标和转换过程。
// Traveling from server to server.
UENUM()
enum ETravelType
{
/** Absolute URL. */
TRAVEL_Absolute,
/** Partial (carry name, reset server). */
TRAVEL_Partial,
/** Relative URL. */
TRAVEL_Relative,
TRAVEL_MAX,
};
void UEngine::SetClientTravel( UWorld *InWorld, const TCHAR* NextURL, ETravelType InTravelType )
{
FWorldContext &Context = GetWorldContextFromWorldChecked(InWorld);
// set TravelURL. Will be processed safely on the next tick in UGameEngine::Tick().
Context.TravelURL = NextURL;
Context.TravelType = InTravelType;
[...]
}
粗略的流程是UE在OpenLevel的时候, 先设置当前World的Context上的TravelURL,然后在UEngine::TickWorldTravel的时候判断TravelURL非空来真正执行Level的切换。
总而言之,WorldContext既负责World之间切换的上下文,也负责Level之间切换的操作信息。
GameInstance
GameInstance里会保存着当前的WorldConext和其他整个游戏的信息。这是一个层次更高的概念。
在Unity,存在一个Application对整个游戏进行管理,它都是玩家能直接接触到的最根源的操作类。
而UE的GameInstance因为继承于UObject,所以就拥有了动态创建的能力,我们可以通过指定GameInstanceClass来让UE创建使用我们自定义的GameInstance子类。所以不论是C++还是BP,我们通常会继承于GameInstance,然后在里面编写应用于整个游戏范围的逻辑。
是一个正在运行的游戏的高级别的管理对象,在游戏创建时生成,游戏实例关闭时销毁,一个游戏中可以有多个GameInstance;GameInstance在切换Level是不会被销毁。
Engine
此处UEngine分化出了两个子类:UGameEngine和UEditorEngine。众所周知,UE的编辑器也是UE用自己的引擎渲染出来的,采用的也是Slate那套UI框架。
UGameEngine.Init()来创建GameInstance。
所以本质上来说,UE的编辑器其实也是个游戏!我们是在编辑器这个游戏里面创造我们自己的另一个游戏。
话虽如此,但比较编辑器和游戏还是有一定差别的,所以UE会在不同模式下根据编译环境而采用不同的具体Engine类,而在基类UEngine里通过一个WorldList保存了所有的World。
- Standlone Game:会使用UGameEngine来创建出唯一的一个GameWorld,因为也只有一个,所以为了方便起见,就直接保存了GameInstance指针。
- 而对于编辑器来说,EditorWorld其实只是用来预览,所以并不拥有OwningGameInstance,而PlayWorld里的OwningGameInstance才是间接保存了GameInstance.
目前来说,因为UE还不支持同时运行多个World(当前只能一个,但可以切换),所以GameInstance其实也是唯一的。
提前说些题外话,虽然目前网络部分还没涉及到,但是当我们在Editor里进行MultiplePlayer的测试时,每一个Player Window里都是一个World。如果是DedicateServer模式,那DedicateServer也会是一个World。
最后实例化出来的UEngine实例用一个全局的GEngine变量来保存。至此,我们已经到了引擎的最根处:
//UnrealEngine\Engine\Source\Runtime\Engine\Private\UnrealEngine.cpp
ENGINE_API UEngine* GEngine = NULL;
GamePlayStatics
既然我们在引擎内部C++层次已经有了访问World操作Level的能力,那么在暴露出的蓝图系统里,UE为了我们的使用方便,也在Engine层次为我们提供了便利操作蓝图函数库。
UCLASS ()
class UGameplayStatics : public UBlueprintFunctionLibrary
我们在蓝图里见到的GetPlayerController、SpawActor和OpenLevel等都是来至于这个类的接口。这个类比较简单,相当于一个C++的静态类,只为蓝图暴露提供了一些静态方法。在想借鉴或者是查询某个功能的实现时,此处往往会是一个入口。