# 概述

有两个主要处理游戏信息的类:GameMode和GameState。

即便是大部分开放式的游戏也有一些基本规则,这些规则构成了GameMode,在一些最基本的关卡里,这些规则包含:

  • 玩家数量和观战者,包括他们的最大数量。
  • 玩家如何进入游戏,包括出生点位的规则。
  • 游戏是否可被暂停,暂停游戏时应该如何处理。
  • 关卡之间的过渡,包括游戏是否应该以开场动画开始。

当游戏中与某些规则相关的事件发生,需要被记录并告诉所有玩家,这些信息会在GameState中保存并同步,这些信息包括:

  • 游戏已经运行了多长时间,包括在本地玩家加入前已经运行多少时间。
  • 当每个玩家加入游戏时,玩家的当前状态。
  • 当前GameMode的基类。
  • 游戏是否已经开始。

# GameModes

尽管有一些共同的基本规则,比如游戏需要的玩家数量,或者玩家加入游戏的方式,对于大部分游戏来说是一样的,但是针对具体某开发游戏的规则还是有很多规则变体的。撇开这些规则先不说,GameMode被用来设计和实现他们,通常有两种被常用的GameModes基类。

在4.14版本的时候引入了AGameModeBase,这是所有GameModes的基类,它是经典类AGameMode的简化和改进版本。在4.14之前主要是AGameMode,但在4.14之后,AGameMode是AGameModeBase的子类。AGameMode更适合创建标准游戏类型,比如根据匹配创建的多人射击游戏;AGameModeBase因为它的简洁和高效而作为新工程的的默认GameMode。

# AGameModeBase

所有的GameMode都是AGameModeBase的子类,它包含相当多可重写的基础函数,下面是一些比较常见的:

函数名 目的
InitGame 会在所有其他脚本之前调用,包括PreInitializeComponents,被AGameModeBase用来初始化参数和生成工具类。它会被任何Actor运行PreInitializeComponents之前调用,包括GameMode自身也是。
PreLogin 接受或拒绝尝试加入服务器的玩家,如果给ErrorMessage设置非空字符串,它会导致Login函数失败,PreLogin在Login之前调用,在Login调用之前可能会等很长时间,尤其是加入的玩家需要下载游戏内容时。
PostLogin 在成功登录后调用,这是第一个比较安全地调用PlayerController的被复制函数的地方。OnPostLogin可在蓝图中添加更多的逻辑。
HandleStartingNewPlayer 在PostLogin之后调用,可在蓝图中重写新加入的玩家应该干什么,默认地,它会创建玩家的Pawn。
RestartPlayer 在开始生成玩家Pawn的时候调用,如果你想指示Pawn的生成位置可在RestartPlayerAtPlayerStart和RestartPlayerAtTransform里实现。这个函数调用完后可在蓝图中实现OnRestartPlayer 来增加更多的逻辑。
SpawnDefaultPawnAtTransform 该函数会实际生成玩家的Pawn,可在蓝图中重写。
Logout 当玩家离开游戏或被关闭时调用,在蓝图中可在OnLogout中增加更多逻辑。

一个AGameModeBase的子类可用来创建游戏中的匹配模式,任务类型,特殊区域。一个游戏可以有任意多的GameMode,都是AGameModeBase的子类;然而,在任意时间只有一个GameMode被使用,当每次使用UGameEngine::LoadMap()打开关卡时,GameMode Actor会在关卡初始化的时候初始化。

GameMode不会被复制到任何加入多人游戏的远程客户端;它只存在于服务器上,所以本地客户端可以看到哪个GameMode被使用,却获取不到实例以及里面的变量。如果玩家确实想更新同GameMode相关信息,可以通过AGameStateBase Actor来保存并同步,这个对象同GameMode一同创建,被复制到所有远程客户端。

# AGameMode

它是AGameModeBase的子类,有一些额外支持多人匹配的工具函数。所有新创建的项目默认使用AGameModeBase,但是如果你需要那些额外支持的功能,可以切换到AGameMode上。如果你继承自AGameMode,你也可以从AGameState继承游戏状态,它会支持比赛状态机。

AGameMode包含能记录比赛状态或者游戏流程的状态机,为了查询当前状态,你可以使用GetMatchState,或者是使用包装函数,比如HasMatchStarted,IsMatchInProgress和HasMatchEnded。下面是一些可能出现的比赛状态:

状态 描述
EnteringMap 这是最初始的状态,Actors还没有Ticking,World还没有被初始化。它会在东西全部加载的时候过渡到下一个状态。
WaitingToStart 在进入这个状态的时候调用HandleMatchIsWaitingToStart,此时Actors开始Ticking,但是玩家还没有生成出来,如果ReadyToStartMatch返回true或StartMatch被调用,它会过渡到下一个状态。
InProgress 这是游戏的主要状态,在进入该状态时会调用HandleMatchHasStarted,这时会调用所有Actors的BeginPlay,这时游戏是处于正常进程中,当ReadyToEndMatch返回true或EndMatch被调用时,比赛会过渡到下一个状态。
WaitingPostMatch 这是倒数第二个状态,进入该状态时调用HandleMatchHasEnded。Actors依旧在Ticking,但是新玩家不能加入进来,当关卡开始切换时,会进入下一个状态。
LeavingMap 这是正常流程的最后一个状态,在进入时会调用HandleLeavingMap,切换到新关卡时会进入EnteringMap状态。
Aborted 这时失败的状态,通过调用AbortMatch进入,当有一个不可恢复的错误时,可以调用它。

比赛状态常常是InProgress,因为在BeginPlay被调用,Actors开始Ticking都是在这个状态里。但是,每个单独的游戏都会用更复杂的规则重写这些状态,比如在等待其他玩家加入一个多人射击游戏时,允许玩家在关卡里自由飞行。

# GameMode蓝图

可以直接从GameMode类创建蓝图,该蓝图设置如下选项:

  • Default Pawn Class
  • HUD Class
  • PlayerController Class
  • Spectator Class
  • Game State Class
  • Player State Class

另外,GameMode的蓝图很有用是因为它们不用改变代码就能调整一些变量,因此比较适合用来将一个GameMode应用到多个关卡,而不用程序员将资产引用硬编码到代码,每次有微小改变时都要改变代码。

# 设置GameMode

下面有几种方法设置关卡的GameMode,下面从最低级到最高级依次列出:

  • 在DefaultEngine.ini文件的/Script/EngineSettings.GameMapsSettings部分设置GlobalDefaultGameMode,这会设置工程中所有地图的默认GameMode。

    [/Script/EngineSettings.GameMapsSettings]
    GlobalDefaultGameMode="/Script/MyGame.MyGameGameMode"
    GlobalDefaultServerGameMode="/Script/MyGame.MyGameGameMode"
    
    1
    2
    3
  • 重写每个地图的项目设置,在World Settings面板中的GameMode Override设置。

  • 在游戏运行时可以传入一些URLs参数,使用game选项来设置游戏模式。

    UE4Editor.exe /Game/Maps/MyMap?game=MyGameMode -game
    
    1
  • 最后在DefaultEngine.ini中的/Script/Engine.WorldSettings/部分来设置。

    [/Script/EngineSettings.GameMapsSettings]
    +GameModeMapPrefixes=(Name="DM",GameMode="/Script/UnrealTournament.UTDMGameMode")
    +GameModeClassAliases=(Name="DM",GameMode="/Script/UnrealTournament.UTDMGameMode")
    
    1
    2
    3

# GameState

GameState负责让客户端知道游戏的状态。从概念上,GameState应该管理所有连接客户端应该知道的信息,它应该和某个具体的GameMode有关,而和每个玩家没有关系。它能记录游戏范围内的属性,比如连接的玩家列表,在Capture The Flag中的团队分数,在开放世界中完成的任务等等。

GameState不是记录玩家相关信息的最好地方,比如某个玩家在CTF比赛中得了多少分数因为它能被PlayerState很好的处理。总之,GameState应该记录下游戏中改变的属性值,这些值和每个玩家有关且对每个玩家可见。GameMode只存在于服务器上,但GameState存在于服务器并复制给所有玩家,在游戏时让所有连接的客户端都同步信息。

AGameStateBase是基本实现,它包含的默认函数为:

函数 说明
GetServerWorldTimeSeconds 这是UWorld函数GetTimeSeconds的服务器版本,它被客户端和服务器同步,能它们之中很可靠的复制。
PlayerArray 所有APlayerState对象的数组,在对所有玩家都实施一些事情的时候很有用。
HasBegunPlay 在游戏中的Actors的BeginPlay被调用时返回true。

AGameStateBase常被C++或蓝图扩展,来包含额外的变量,函数,用来通知玩家游戏中正在发生的事。它的改变常常是因为它所依赖的GameMode改变了而改变。GameMode自己也可以使用继承自AGameStateBase的C++类或蓝图重写默认的GameState。