赞
踩
要构建多人的网络游戏,理解UE4的网络框架和原理是十分必要的。所以在这里
对UE4的网络框架做一个简单总结,一些内容来源于自己的理解,还有一些来源于对官方文档和引用文章的翻译。
UE4的网络框架与许多常见框架一样采用了 C/S的客户端 – 服务器模型架构 ,这就意味着服务器有绝对**权威(authoritative)**决定关键的行为,客户端一般将动作请求发送到服务器由服务器进行校验,然后服务器处理数据变化,再将结果同步回客户端进行展示和模拟。
在实现网络通信和同步上,UE4操作的基本对象就是Actor, 所以参与同步的类不是直接或者间接继承自Actor C++基类AActor, 就是作为某个Actor的组成部分。
在通信机制上,UE4对Actor提供了两种基本机制,一种是同步(Replication), 一种是远程过程调用**RPCs(Remote Procedure Calls)。**同步是UE4自动管理进行的,用于频繁发生变化的属性和数据的同步;RPCs由程序主动调用,类似于函数调用,只不过调用和执行的机器并不是在同一个地方。之后会详细介绍这两种机制。
UE4定义了几种网络模式, 都有专门的的枚举来表示,表示当前运行程序在网络同步中的角色或者作用,它们分别是:
NM_Standalone : 表示服务器运行在单机状态,不接受任何其他连接客户端的请求。可以看做是没有网络功能的单机程序,一般用于单人游戏或者本地游戏。
NM_DedicatedServer : 专用服务器(Dedicated Server),类似于传统中心C/S网络结构中的服务器角色,它本身不包含任何客户端。只运行服务器的逻辑,所以省略可以不运行不必要的声音,画面, 用户输入等客户端专有的处理逻辑。
NM_ListenServer :包含了本地Player的服务器模式。这个模式既包含服务器部分的功能又包含客户端部分的功能。但是由于本身也是客户端,所以在此机器上运行的逻辑和决策并不是完全可信的。一般这种模式用于几个人联机点对点的服务,比如以前的魔兽RPG的开房间模式,其中一个客户端作为服务器管理中转其他客户端的信息。
NM_Client : 纯客户端的网络模式,当处于这个模式,客户端是连接到远程的Dedicated Server专用服务器或者Listen Server上,不会运行服务器端的逻辑。
下面会详细列列举UE4网络相关的一些基础知识。
就像之前提过的,服务器运行方式分为2种类型,分别是 Listen Server 和 Dedicated Server 。
Dedicated Server是 专用服务器 ,是作为一个 独立运行 的服务器,不需要与任何客户端绑定。
Dedicated Server与客户端分离运行,一般作为一个中心服务器让客户端加入或者离开。
它可以被编译在Windows和Linux平台运行,也可以运行在Virtual Server虚拟服务器,让客户端通过固定的IP地址连接。
Dedicated Server不需要显示部分,所以不需要UI,不需要PlayerController。也没有专属的角色在游戏中。
ListenServer是 客户端 和 服务器端结合 的一种服务器类型,所有至少会有一个Client连接到这个服务器,当客户端关闭,服务器也会关闭。
ListenServer需要有UI和PlayerController, 可通过PlayerContoller[0]来获得。
ListenServer的IP地址就是运行客户端的地址,所以可能会产生没有固定的静态IP的问题。这个问题可以由OnlineSubsystem子系统来解决。
Actor是网络交互中的 第一类对象 ,所以要进行网络信息的同步传输,首先要找到需要同步的Actor。为了使用网络功能,必须把Actor的bReplicates属性设置为True,这个属性在C++中或者蓝图中都可以使用。
在Actor的网络交互机制上,主要分为2种类型,分别是 Replication同步 和Remote Procedure Calls(RPCs)远程过程调用。
Replication同步 , 主要用于进行属性的自动同步。一般用于一些经常变化的属性,比如角色的HP血量, 移动位置等。
RPCs远程过程调用 ,由调用者主动发起。一般用于一些通知事件的发生,或者发起一些行为和动作。比如使用武器开火射击,或者释放了一个技能
Replication属性同步一般是指服务器将数据传递到客户端的动作(反之不行)。进行同步的方式分为两种, Property Replication属性同步 和 Component Replication组件同步 。
顾名思义, Property Replication属性同步 是以对象属性作为基本同步单位的同步机制;而 Component Replication组件同步 是以Actor组件为基本管理单位的同步机制,相对于属性同步属于更粗粒度的管理。
下面会具体介绍两种机制和使用方式。
要使用属性同步,首先要保证Actor已经把Replicates标记开启了,这个是所有网络机制启用的基本保证。设置方式既可以通过蓝图勾选,也可以同步代码设置,如下图:
接下来,要将声明的属性设为Replicated,同样也可以通过C++和蓝图两种方式:
接下来,在属性所在的Actor类中,需要声明GetLifetimeReplicatedProps 函数,并调用UE4内置宏添加这个属性同步的调用语句,如下:
第一个参数是Actor的子类名,第二个是属性名。
除了默认的同步调用,我们也可以使用 条件同步 调用, 使用的是另一个宏 DOREPLIFETIME_CONDITION ,如图:
当使用条件同步时,只有满足了条件所对应的情况才会进行属性同步,这个例子中 COND_OwnerOnly 对应其中一种条件值得枚举值,表示属性只会同步给Actor 的拥有者(Onwer)。此外还有以下其他条件枚举值可以选择:
条件 | 说明 |
---|---|
COND_InitialOnly | 属性只会在 第一次 连接时候同步,之后不再进行同步 |
COND_OwnerOnly | 属性只会同步给Actor的 拥有者(Owner) |
COND_SkipOwner | 属性会同步给 除了 Actor 拥有者 之外其他的所有连接 |
COND_SimulatedOnly | Actors 属性只同步给**模拟(simulated)**的Actor |
COND_AutonomousOnly | 属性只同步给 Autonomous自治 类型的Actor |
COND_SimulatedOrPhysics | 属性会同步给模拟(Simulated)类型和**物理类型(设置了bRepPhysics标记的)**的Actor |
COND_InitialOrOwner | 属性会在 第一次连接 时候同步 或者 是同步给 Actor的拥有者 |
COND_Custom | 自定义同步条件,使用 SetCustomIsActiveOverride 来开关同步 |
有时候可能想在属性 同步之后 得到通知,或者调用一个回调函数,UE4提供了 RepNotify 机制来实现这个功能。这个机制能让用户提供一个回调函数,在当属性值发生更新之后,就会调用这个回调函数,这个机制对 所有同步的实例 都起作用。
在蓝图里,当选择 Replication类型为RepNotify之后,蓝图会自动创建一个回调函数,一般名称为OnRep_***各式, 如下图:
如果在C++代码中,可以在 UPROPERTY宏 中添加声明, 并手动添加一个同步通知函数,如下:
这个例子在Health属性同步声明中,指定了OnRep_Health函数作为其RepNotify的函数,要注意这个函数必须添加 UFUNCTION 的宏声明,即使这个宏里是空的声明。
当Health属性更新后,就会调用 OnRep_Health通知函数。在实现中可以添加特殊的逻辑,比如以下的逻辑判断如果Health小于0,则播放死亡动画。
除了使用同属性同步,UE4也支持以功能**组件(Component)**的方式进行同步。一般情况下,大部分组件是不需要同步的,需要同步的属性一般都在Actor下通过属性同步方式完成。但有一些特殊Component包含了需要网络同步的属性,这种情况下就需要使用组件同步的功能。
组件同步的方式与Actor的方式基本一致,组件也可以使用属性同步,或者调用RPCs来进行网络交互。组件可以看作是Actor的一部分,当Actor发生同步时,就会递归调用到组件的同步。组件同步属性时,也需要调用相应的**::GetLifetimeReplicatedProps (…)**函数像Actor一样添加同步属性的设置。
当使用组件同步时,组件可以分为两个大类,分别是静态组件(Static Component)和动态组件(Dynamic Component)。
静态组件是Actor创建时候就会生成的组件,无论是否进行组件同步,都会创建这个组件。服务器也不会通知客户端创建这类组件,客户端创建Actor的时候就会直接生成,一般这种组件是在C++构造函数Constructor或者作为默认的子物体DefaultSubObject在蓝图里创建。一般只有这类组件的属性发生变化,才需要进行服务器和客户端之间的同步。
动态组件是指在运行时动态创建的组件,在服务器创建的动态组件会自动同步到所有客户端实例然后分别进行创建,之后也会在属性变化时候自动同步属性给所有客户端。
客户端虽然也可以创建自己本地的动态组件,但是这种情况下组件并不会同步到服务器,也不会同步给其他客户端,所以在服务器和其他客户端是看不到新创建的组件的。
使用组件同步的方式基本和Actor一样,都是先开启同步的标记属性,然后在GetLifetimeReplicatedProps函数添加具体要同步属性的规则宏。
要在C++开启组件同步,需要调用组件类的**AActorComponent::SetIsReplicated(true)**方法。如果组件是Actor的默认子对象,那么需要在构造函数生成组件之后进行调用开启同步,如下图:
在蓝图中的组件属性面板中,有 Component Replicates组件同步 的属性勾选项,只要勾上就可以开启。
还有一种方式可以通过在蓝图中 调用函数 的方式开启,蓝图函数是 Set Is Replicated , 如图:
RPCs 是**(Remote Procedure Cal)远程过程调用**的缩写,表示一个函数在本地进行调用,但是在远端执行。
RPCs既可以是从 服务器调用到客户端 ,也可以是 从客户端调用到服务器 。在使用上,一般用于调用一个事件或者动作,比如服务器告诉客户端某个NPC释放了一个技能,客户端进行特效,声音的播放。或者客户端告诉服务器自己更换了一个武器,服务器处理物品的更换,属性变化,以及同步给其他客户端的一些逻辑等。
RPCs分为三种类型,分别是:
在使用RPCs进行网络通信,必须满足一些特定的需求和注意事项:
为了更清晰,不同类型RPCs在不同角色的机器上和不同的拥有关系下的执行效果,下边列出了两种情景下的调用效果表。第一种是在服务器调用的效果;第二种是在客户端调用的效果。
表的不同列表示RPCs的不同类型,表的行表示被调用Actor对应不同的拥有关系。
Actor拥有关系 | 非同步 | NetMulticast | Server | Client |
---|---|---|---|---|
客户端拥有 | 在服务器执行 | 在服务器和所有客户端执行 | 只在服务器执行 | 在拥有者的客户端执行 |
服务器拥有 | 在服务器执行 | 在服务器和所有客户端执行 | 只在服务器执行 | 在服务器执行 |
未被拥有 | 在服务器执行 | 在服务器和所有客户端执行 | 只在服务器执行 | 在服务器执行 |
Actor拥有关系 | 非同步 | NetMulticast | Server | Client |
---|---|---|---|---|
调用客户端拥有 | 在调用客户端执行 | 在调用客户端执行 | 在服务器执行 | 在调用客户端执行 |
其他客户端拥有 | 在调用客户端执行 | 在调用客户端执行 | 丢弃 | 在调用客户端执行 |
服务器拥有 | 在调用客户端执行 | 在调用客户端执行 | 丢弃 | 在调用客户端执行 |
未被拥有 | 在调用客户端执行 | 在调用客户端执行 | 丢弃 | 在调用客户端执行 |
声明RPCs很简单,只需要在C++头文件对应的函数增加 UFUNCTION 宏,里边写入函数的RPCs类型关键字,如下:
Client类型RPCs :
Server类型RPCs:
NetMulticast多播类型RPCs:
RPC有一个隐含的属性 —— 可靠性Reliability 。 默认情况下, RPCs是不可靠的,也就是无法保证肯定会在目标机器上执行,可能会丢失。但是也可以手动开启关键字 Reliable 的标签,保证函数肯定会执行, 如下:
也可以添加 Unreliable 的关键字标签,但是因为默认就是Unreliable的,所以一般并不需要。
一般情况下,Reliable用于实现一些关键函数事件,尤其是会对逻辑流程产生依赖影响的函数。而Unreliable的函数适合播放一些无关紧要的效果,声音等,即使偶尔没有执行也不会产生严重影响。Unreliable的RPCs在执行上会有更高的性能效率。
要在C++实现RPCs函数,需要实现一个 (函数名)_Implementation的函数体,加后缀_Implementation是UE4默认的规则,如下声明了一个名叫Server_PlaceBomb的函数:
然后添加了这个函数的实现体
有时候开发者希望在调用RPCs函数之前进行一些条件检查,并不是每次都必然会执行这个函数实现,或者在检测到一些特定的输入时候执行一些特定的操作。UE4提供了这样的机制,就是 Validation验证机制 。
要对一个RPCs函数使用验证,只需要在声明时候在 UFUNCTION 宏中额外添加 WithValidation 关键字,如下:
然后需要找一个地方添加验证函数, 函数名默认是 原函数名_Validate ,如下:
在这个验证函数中,验证RPCs参数 AddHealth 是否大于最大加血量 MAX_ADD_HEALTH , 如果超过了就返回false, 这样会主动断开调用者的连接,认为是作弊者;否则就返回true, 通过验证。
除了C++, 也可以再蓝图中使用RPCs函数。使用方法是首先在蓝图里生命一个自定义事件Custom Event, 然后选择Replicates对应的RPCs函数类型。因为RPCs无法返回值,所以无法使用一般函数来创建它们。如图:
http://cedric-neukirchen.net/2017/02/14/multiplayer-network-compendium/
https://docs.unrealengine.com/en-US/Gameplay/Networking/index.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。