本系列简单总结下UE的Gameplay框架是怎么样的,基本上是博主看文档+实践之后得到的个人理解,如果有问题欢迎评论区指出。
简单框架
这里引用UE官网文档的框架图,首先游戏本身有 GameMode
和 GameState
两大类,GameMode
由游戏的基础规则构成,而基于游戏规则发生的事件产生的信息由 GameState
保存并同步给客户端。
玩家通过 PlayerController
加入到游戏中,既然是和玩家直接交互,必然要提供一些与玩家相关的接口/功能,比如 Input
接收玩家的输入(键盘/鼠标/手柄),HUD
提供UI显示(比如玩家的血条),PlayerCameraManager
管理玩家的相机。
而且 PlayerController
作为中间层,接收到玩家的输入,会去操作其管理的 Pawn
,比如移动、攻击等。这里也可以看到 Pawn
既可以被 PlayerController
控制也可以被 AIController
控制,并且需要注意的是,PlayerController
可以随时 Possess 和 UnPossess 一个 Pawn
,可以想象一个玩家其实可以切换不同角色进行游戏的,而且这些角色本身对于玩家的输入可以有不同的响应,通过 PlayerController
这一层将玩家(玩家的输入/提供给玩家的画面/UI)和玩家操作的角色解耦是很直观的吧。
这里还需要注意的是,有些数据/属性是玩家固有的(比如玩家的命数/玩家获得的金币数),这些信息保存在 Pawn
里是不合适的,保存在 PlayerController
也是不那么合适的,特别是考虑的多人游戏的场景,PlayerController
本身是不会同步到其它的客户端的,那么其它客户端想获取这个数据就困难了。所以UE里还有一个类 PlayerState
用来保存与玩家强相关的数据/属性。
GameMode 和 GameState
下面引用自UE官方文档
两个主要类负责处理进行中游戏的相关信息:Game Mode
和 Game State
。
即使最开放的游戏也拥有基础规则,而这些规则构成了 Game Mode
。在最基础的层面上,这些规则包括:
- 出现的玩家和观众数量,以及允许的玩家和观众最大数量。
- 玩家进入游戏的方式,可包含选择生成地点和其他生成/重生成行为的规则。
- 游戏是否可以暂停,以及如何处理游戏暂停。
- 关卡之间的过渡,包括游戏是否以动画模式开场。
基于规则的事件在游戏中发生,需要进行追踪并和所有玩家共享时,信息将通过 Game State
进行存储和同步。这些信息包括:
- 游戏已运行的时间(包括本地玩家加入前的运行时间)。
- 每个个体玩家加入游戏的时间和玩家的当前状态。
- 当前 Game Mode 的基类。
- 游戏是否已开始。
从官网文档给出的定义能看出 GameMode
用来定义和实现规则,GameState
用存储和同步游戏中的信息(这里的信息感觉是和个体无关的,是整个游戏的信息),对于多人联机游戏来说, GameMode
本身只存在于服务器,所以要同步信息需要一个 GameState
,其随 GameMode
创建,之后被复制到所有远程客户端。
在大钊的InsideUE5中有提到UE将逻辑和资源场景进行了解耦,World 更多的是逻辑的概念,而Level是资源场景表示,一场游戏中,玩法再复杂但也只有一个,场景却可以无限大,所有可以有很多个场景区域拼接组装而成,因此是World包含Level,而不是反过来。而 GameMode
和 GameState
我个人理解就是依附于 World 存在的。
下面我们从 GameMode
和 GameState
给出的方法和属性出发,更具体地分析一下。
GameMode
首先4.14版本之前,AGameMode
是 GameMode
的基类,因为做UE的公司本身自己也做游戏嘛,主要是做带有竞技性的多人联机游戏,那么可能对于一些游戏类型,AGameMode
本身太过庞大了,有些功能是没必要的。所以在 4.14 版本加入了 AGameModeBase
作为所有 Game Mode
的新基类,其本身更简洁高效,是新代码项目中包含的全新默认游戏模式。
然后还需要注意的是,并不是一款游戏只能有一个 GameMode,对于游戏的不同比赛模式、任务类型或特殊区域都可以创建不同类型的 GameMode。但是一个时间只能使用一个 Game Mode,每次关卡进行游戏实例化时 Game Mode Actor 将通过 UGameEngine::LoadMap()
函数进行实例化。
GameMode的功能可以简单总结如下:
- 游戏内实体的spawn
我们在GameMode中指定的一些类,就是为了让GameMode能够知道具体要创建的对象的类
- 游戏内状态
比如生成一个玩家pawn(重生玩家),暂停游戏 - Level的切换
准确地说是World的切换时的行为,比如决定刚进入一场游戏的时候是否应该播放开场动画(cinematic),切换到下一个关卡是否要使用无缝漫游(bUseSeamlessTravel
),一旦使用无缝漫游,可以重载GameMode
和PlayerController
的GetSeamlessTravelActorList
方法来指定哪些Actors不被释放而进入下一个World的Level。
无论是否使用无缝漫游,当前的World都会被释放掉,然后加载创建新的World,所以要小心GameMode
里保存的状态消失。
void AGameMode::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
{
UWorld* World = GetWorld();
// Get allocations for the elements we're going to add handled in one go
const int32 ActorsToAddCount = World->GameState->PlayerArray.Num() + (bToTransition ? 3 : 0);
ActorList.Reserve(ActorsToAddCount);
// always keep PlayerStates, so that after we restart we can keep players on the same team, etc
ActorList.Append(World->GameState->PlayerArray);
if (bToTransition)
{
// keep ourselves until we transition to the final destination
ActorList.Add(this);
// keep general game state until we transition to the final destination
ActorList.Add(World->GameState);
// keep the game session state until we transition to the final destination
ActorList.Add(GameSession);
// If adding in this section best to increase the literal above for the ActorsToAddCount
}
}
- 多人游戏的步调同步
是AGameMode
提供的一些额外的功能以支持多人游戏。其包含一个跟踪比赛状态或整体游戏流程的状态机。
GameState
GameState
正如前文所述,其应该管理所有已连接客户端已知的信息(特定于 GameMode 但不特定于任何个体玩家)。它能够追踪游戏层面的属性:
- 已连接玩家的列表,通过
PlayerArray
获取到所有连接玩家的APlayerState
对象的阵列 - 同步服务器的时间戳,通过
GetServerWorldTimeSeconds
将在客户端和服务器上同步 - 使用
HasBegunPlay
判断一个Actor是否调用过BeginPlay
- 保存并同步其它与游戏有关而与个人无关的数据,比如开放世界游戏中已完成的任务,夺旗游戏中的团队得分等。