Contents

UE4网络教程1-网络架构总揽

一.网络总览

Unreal Engine4使用标准的Server-Client体系结构。 这意味着服务器是权威的,所有数据必须首先从客户端发送到服务器。 然后服务器依赖你写的代码验证数据并且给予反馈。

1.1一个简单的例子

在多人匹配中,作为一个客户端,当你移动你的角色时,你并不是自己移动你的角色,而是告诉服务器你想移动它。 然后服务器为其他人(包括你)更新你的位置,。

注意:为了防止对本地客户端产生“滞后”的感觉,程序员经常让这个玩家直接在本地控制他们的角色,虽然服务器仍然可能在客户端开始作弊的时候覆盖角色的位置!这意味着,客户端将(几乎)永远不会直接与其他客户端“交谈”。

1.2其他的例子

当向另一个客户端发送聊天消息时,您实际上是将其发送到服务器,然后服务器将其传递给您想要到达的客户端。 也许你消息的结构方并不是一个人,也就是不只是一次私聊,对方有可能是是一个team,guild,group等。

永远不要相信客户! 信任客户端意味着在执行客户端操作之前不测试客户端操作。 这将允许作弊!不过有些情况下我们可能会允许一些纯粹表现的效果只在客户端进行而不在客户端验证。一个简单的例子是射击:在服务器上应当确保对某个客户端的测试,如果在服务器的记录中显示该客户端实际上有弹药,才应当允许该客户端射击,而不是直接在客户端处理射击!

二.UE4 的网络框架

我们如果拥有一个对象,使用前面关于UE4的Server-Client体系结构的信息,我们可以将框架分成4个部分:

  1. Server Only – 只在服务器上
  2. Server & Clients – 同时存在客户端和服务器
  3. Server & Owning Client – 只在服务器和拥有他的客户端上
  4. Owning Client Only – 这些对象只存在于客户端上

“Owning Client”是一个所有权的问题,也就是这个对象的所有权是否是在你这一台客户端上,例如你控制的角色,这个对象对于你来说,归属权就是Owning Client。 所有权在以后的“RPC”中变得很重要。下图展示了一些常见的类以及它们存在的部分。这就是一些最重要的类是如何在网络框架中布局的。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206124449.png

下图显示了另一个例子(具有两个客户端的专用服务器),说明这些类的对象如何通过网络框架分发: https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206124556.png

之后将展示一些最常见的类。 还将提供如何使用这些类的小示例。

如果不理解,请忽略,直到您阅读系列后面有关复制等章节的内容。

一些游戏类型可能会使用不同方式使用这些类。 下面的例子和解释不是唯一的方法

2.1 GameMode

4.14开始GameMode类被分成 GameModeBase和GameMode。 GameModeBase的功能较少,因为有些游戏可能不需要GameModeBase类的完整功能。

GameMode用于定义游戏的规则。 这包括如Apawn、Player Controller、APlayer State等。 它只在服务器上可用。 客户端没有这个对象,并且在试图检索它时只会得到一个nullptr。

GameMode例如死亡匹配,团队死亡匹配或捕获旗帜等模式。这意味着游戏模式可以定义如下内容:

  1. 我们是团队比赛还是个人竞赛
  2. 获胜条件是什么?一个人/一个团队需要多少次杀戮?
  3. 如何取得胜利? 杀了人? 夺旗?
  4. 将使用哪些角色? 允许使用哪些武器? 只有手枪?

在多人游戏中,GameMode也有一些有趣的功能,帮助我们管理玩家和/或一般的比赛流程。

2.2.1GameMode Blueprint

我们要做的第一步是蓝图版本的“覆盖函数”部分: https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206222811.png

您可以为这些函数实现您自己的逻辑,以适应您的游戏的特定规则。 这包括改变Gamemode产生DefaultPawn的方式或者你想决定游戏是否准备开始: 例如,检查是否所有玩家都已加入并且已经准备好了:

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206222934.png

但也有一些事件(Events),你可以用来对某些情况作出反应。我经常使用的一个很好的例子我经常使用,是Event OnPostLogin

每当一个新玩家加入游戏时,就会调用它。

稍后,您将了解更多关于 Connecting Process

事件传递给您一个有效的PlayerController,这个控制器是正在连接的player所持有的。

这个事件可以用来与这个Player交互,为他生成一个新的pawn,或者只是将他的PlayerController保存在一个数组中供以后使用。 https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206223820.png

如前所述,您可以使用GameMode来管理您的游戏的一般匹配流程。 这也链接到函数,您可以重写这些函数(如Ready to start Match)。

这些函数和事件可以用来控制当前匹配状态。 当“Ready to start Match”函数返回true时,它们中的大多数将被自动调用,但您也可以手动使用它们。

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206224043.png

New State是一种简单的FName类型。 你现在可以问,“为什么在GameState中没有处理这件事?”

是的,这些 GameMode功能实际上是与GameState携手工作。 他只是给你一个位置来管理远离客户端的State,因为GameMode只存在于服务器上!

https://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206224453.png

当然,GameMode也有重要的变量,你可以使用。 这是已经继承的(inherited)变量列表。 其中一些可以通过GameMode的Blueprint的类默认设置(ClassDefaults):

这些变量中的大多数的命名都能够自我解释。

  1. DefaultPlayerName,它使你能够给每个连接的Player一个可以通过PlayerState类访问的默认PlayerName。
  2. bDelayedStart,这将阻止比赛开始,即使‘Ready to start Match’符合所有其他标准。
  3. 其中一个更重要的变量是所谓的Options String。这些选项用分?隔,您可以通过OpenLevel函数或者当你调用ServerTravel作为控制台命令。
  4. 你可以使用Parse Option来提取传递的选项,例如MaxNumPlayershttps://papalqiblog.oss-cn-beijing.aliyuncs.com/blog/picture20210206225757.png

2.2.2GameModeC++

所有的蓝图内容,也可以使用C++。

因为ReadyToStartMatch是一个BlueprintNativeEvent,所以实际的C++函数的实现称为“ReadyToStartMatch_Implementation‘。 这就是我们想要覆盖的:

1
2
3
4
5
/* Header file of our GameMode Child Class inside of the Class declaration */
// Maximum Number of Players needed/allowed during this Match
int32 MaxNumPlayers; 
// Override Implementation of ReadyToStartMatch
virtual bool ReadyToStartMatch_Implementation() override;
1
2
3
4
5
6
/* CPP file of our GameMode Child Class */
bool ATestGameMode::ReadyToStartMatch_Implementation() 
{
    Super::ReadyToStartMatch();
    return MaxNumPlayers == NumPlayers; 
}
1
2
3
4
5
/* Header file of our GameMode Child Class inside of the Class declaration */
// List of PlayerControllers
TArray<class APlayerController*> PlayerControllerList;
// Overriding the PostLogin function
virtual void PostLogin(APlayerController* NewPlayer) override;
1
2
3
4
5
6
/* CPP file of our GameMode Child Class */
void ATestGameMode::PostLogin(APlayerController* NewPlayer) 
{
    Super::PostLogin(NewPlayer);
    PlayerControllerList.Add(NewPlayer);
}

当然,所有的匹配处理函数也可以重写和更改,所以我不会在这里列出它们。

1
2
3
4
5
/* Header file of our GameMode Child Class inside of the Class declaration */
// Maximum Number of Players needed/allowed during this Match
int32 MaxNumPlayers; 
// Override BeginPlay, since we need that to recreate the BP version
virtual void BeginPlay()override;
1
2
3
4
5
6
7
8
/* CPP file of our GameMode Child Class */
void ATestGameMode::BeginPlay()
 {
    Super::BeginPlay();
    // 'FCString::Atoi' converts 'FString' to 'int32' and we use the static 'ParseOption' function of the
    // 'UGameplayStatics' Class to get the correct Key from the 'OptionsString'
    MaxNumPlayers = FCString::Atoi( *(UGameplayStatics::ParseOption(OptionsString, “MaxNumPlayers”)) );
}