前言

本文将会持续总结 ue 的 GamePlay 框架相关内容。

GameModes

ue 中有两个主要类负责处理进行中游戏的相关信息:Game Mode 和 Game State。

即使最开放的游戏也拥有基础规则,而这些规则构成了 Game Mode。在最基础的层面上,这些规则包括:

  • 出现的玩家和观众数量,以及允许的玩家和观众最大数量。

  • 玩家进入游戏的方式,可包含选择生成地点和其他 生成/重生 行为的规则。

  • 游戏是否可以暂停,以及如何处理游戏暂停。

  • 关卡之间的过渡,包括游戏是否以动画模式开场。

基于规则的事件在游戏中发生,需要进行追踪并和所有玩家共享时,信息将通过 Game State 进行存储和同步。这些信息包括:

  • 游戏已运行的时间(包括本地玩家加入前的运行时间)。

  • 每个个体玩家加入游戏的时间和玩家的当前状态。

  • 当前 Game Mode 的基类。

  • 游戏是否已开始。

Game Modes 的任务是定义和实现规则。Game Modes 常用的基类有两个:AGameModeBaseAGameMode

AGameModeBase

所有 Game Mode 均为 AGameModeBase 的子类。而 AGameModeBase 包含大量可重写的基础功能。部分常见函数包括:

函数/事件 目的
InitGame 在任何其他脚本(包括 PreInitializeComponents)之前调用的 InitGame 事件,用于 AGameModeBase 的初始化。
PreLogin 接受或拒绝试图加入服务器的玩家。如果将 ErrorMessage 设置为非空字符串,会导致登录失败。PreLogin 在登录之前调用,尤其是如果加入的玩家需要下载游戏内容,则可能会经过较长时间。
PostLogin 在成功登录后调用。这是第一个可以安全调用 PlayerController 上的复制函数的地方。OnPostLogin 可以在蓝图中实现以添加额外的逻辑。
HandleStartingNewPlayer 在 PostLogin 或无缝旅行之后调用,可以在蓝图中覆盖以更改新玩家的操作。默认情况下,它将为玩家创建一个 Pawn。
RestartPlayer 用于开始生成玩家的 Pawn。还提供了 RestartPlayerAtPlayerStart 和 RestartPlayerAtTransform 函数,如果要指定 Pawn 将生成的位置。可以在此函数完成后在蓝图中实现 OnRestartPlayer 以添加逻辑。
SpawnDefaultPawnAtTransform 实际生成玩家的 Pawn,可以在蓝图中覆盖。
Logout 当玩家离开游戏或被销毁时调用。OnLogout 可以在蓝图中实现以执行蓝图逻辑。

可针对游戏提供的每个比赛模式、任务类型或特殊区域创建 AGameModeBase 的子类。一款游戏可拥有任意数量的 Game Mode,因此也可拥有任意数量的 AGameModeBase 类子类;然而,给定时间上只能使用一个 Game Mode。每次关卡进行游戏实例化时 Game Mode Actor 将通过 UGameEngine::LoadMap() 函数进行实例化。

Game Mode 不会复制到加入多人游戏的远程客户端;它只存在于服务器上。因此本地客户端只能看到之前使用过的留存 Game Mode 类(或蓝图),但无法访问实际的实例并检查其变量,确定游戏进程中已发生哪些变化。

如玩家确实需要更新与当前 Game Mode 相关的信息,可将信息保存在一个 AGameStateBase Actor 上以保持同步。AGameStateBase Actor 随 Game Mode 而创建,之后被复制到所有远程客户端。

AGameMode

AGameMode 是 AGameModeBase 的子类,具有额外的功能以支持多人游戏匹配和传统行为。所有新创建的项目默认使用 AGameModeBase,但如果需要额外的功能,可以切换到继承自 AGameMode。如果继承自 AGameMode,还应该从 AGameState 继承游戏状态,因为它也支持 MatchState 状态机。

AGameMode 包含一个状态机,用于跟踪匹配或一般游戏流程的状态。要查询当前状态,你可以使用 GetMatchState 或像 HasMatchStartedIsMatchInProgressHasMatchEnded 这样的包装函数。以下是可能的 MatchState:

  • EnteringMap 是初始状态。角色尚未进行 Tick,世界尚未完全初始化。当一切都加载完毕时,它将转换到下一个状态。

  • WaitingToStart 是下一个状态,进入时会调用 HandleMatchIsWaitingToStart。角色进行 Tick,但玩家尚未生成。如果 ReadyToStartMatch 返回 true,或者调用 StartMatch,它将转换到下一个状态。

  • InProgress 是游戏的主要部分发生的状态。进入时会调用 HandleMatchHasStarted,然后调用所有角色的 BeginPlay。此时,正常的游戏正在进行。当 ReadyToEndMatch 返回 true 或调用 EndMatch 时,匹配将转换到下一个状态。

  • WaitingPostMatch 是倒数第二个状态,进入时会调用 HandleMatchHasEnded。角色仍在 Tick,但新玩家无法加入。当地图转移开始时,它将转换到下一个状态。

  • LeavingMap 是正常流程的最后一个状态,进入时调用 HandleLeavingMap。匹配在传输到新地图时保持在此状态,然后会转换回 EnteringMap

  • Aborted 是失败状态,可以通过调用 AbortMatch 来启动。当出现不可恢复的错误时,将设置此状态。

以下为 AGameMode.h 中的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
/** Possible state of the current match, where a match is all the gameplay that happens on a single map */
namespace MatchState
{
extern ENGINE_API const FName EnteringMap; // We are entering this map, actors are not yet ticking
extern ENGINE_API const FName WaitingToStart; // Actors are ticking, but the match has not yet started
extern ENGINE_API const FName InProgress; // Normal gameplay is occurring. Specific games will have their own state machine inside this state
extern ENGINE_API const FName WaitingPostMatch; // Match has ended so we aren't accepting new players, but actors are still ticking
extern ENGINE_API const FName LeavingMap; // We are transitioning out of the map to another location
extern ENGINE_API const FName Aborted; // Match has failed due to network issues or other problems, cannot continue

// If a game needs to add additional states, you may need to override HasMatchStarted and HasMatchEnded to deal with the new states
// Do not add any states before WaitingToStart or after WaitingPostMatch
}

匹配状态几乎总是 InProgress,因为这是调用 BeginPlay 并开始角色更新的状态。然而,个别游戏可以覆盖这些状态的行为,以构建具有更复杂规则的多人游戏,例如允许玩家在等待其他玩家加入多人射击游戏时自由飞行。

GameState

Game State 负责启用客户端监控游戏状态。从概念上而言,Game State 应该管理所有已连接客户端已知的信息(特定于 Game Mode 但不特定于任何个体玩家)。它能够追踪游戏层面的属性,如已连接玩家的列表、夺旗游戏中的团队得分、开放世界游戏中已完成的任务,等等。

Game State 并非追踪玩家特有内容(如夺旗比赛中特定玩家为团队获得的分数)的最佳之处,因为它们由 Player State 更清晰地处理。整体而言,GameState 应该追踪游戏进程中变化的属性。这些属性与所有人皆相关,且所有人可见。Game mode 只存在于服务器上,而 Game State 存在于服务器上且会被复制到所有客户端,保持所有已连接机器的游戏进程更新

GamePlay 相关类的网络同步

在 UE 中,与 GamePlay 相关的 Pawn、PlayerController、PlayerState、GameMode、GameState 类各自有不同的网络同步设置。

PlayerController 与 PlayerState

Server 端拥有所有玩家的 PlayerController,客户端仅拥有自己控制的角色的 Controller;而 PlayerState 存在于所有的 Client 与 Server 中。

1.png

因此,对于那些 “只想让操控者(Owner)的客户端看到的效果”,如拾取武器后 UI 的显示等,应当在 PlayerController 中执行。无论是使用 Replicate 还是 RPC,都只会同步到这一个客户端;同样道理,玩家血量等属性放在 PlayerState 中比较合适,可以同步到所有的客户端以用于显示血量等状态。

GameMode 与 GameState

与前文所述一致,GameMode 仅存在于 服务器端,GameState 存在于所有客户端与服务器端。

2.png

因此,GameMode 适合完成 玩家的重生、胜利的判定等对游戏至关重要的功能(GameMode 本就是为了这些功能而设计的);GameState 可以储存当前游戏的一些状态信息如游戏时间与维护当前所有玩家的列表等。

Pawn

Pawn 与 PlayerState 的同步状态相同,同时存在于所有的客户端与服务器上。

3.png

最基本的现实层面的功能应该放在此处完成,如某个客户端开枪,需要同步在其他客户端与服务器上显示开枪的效果,一种合理的实现方法是使用 RPC,在客户端开枪时调用 ServerRPC ,Server 再调用一个 NetMulticast RPC,使得所有的客户端与服务器均展示这个效果。详见 UE5-MPTPS-5