首页 > 其他分享 >UnrealEngine - 网络同步入门

UnrealEngine - 网络同步入门

时间:2023-04-11 15:13:01浏览次数:43  
标签:同步 入门 UnrealEngine Actor RPC 调用 服务器 客户端

1 网络同步机制

UE 提供了强大的网络同步机制:

  • RPC :可以在本地调用,对端执行
  • 属性同步:标记一个属性为 UPROPERTY(Replicated) 就可以自动将其修改后的值同步到客户端
  • 移动复制:Actor 开启了移动复制后会自动复制位置,旋转和速度
  • 创建和销毁:Server 创建 Actor 时根据其权限会在所有连接客户端生成远程代理
    UE 基本上都是基于 Actor 进行同步的。Actor 同步的前提需要标记 Actor 为 bReplicated 。首先来了解下如何应用 UE 中的属性同步。

2 Actor 同步

2.1 如何同步一个 Actor

首先思考一下,如何创建一个 Actor 然后让他同步到各个客户端?

  • 在哪里创建?创建 Actor 的操作显然需要在服务端执行,如果在客户端执行,这个 Actor 只会在这个客户端可见。

image.png|625

  • 如何让 Actor 同步? 标记 Actor 的 bReplicated 为 True。

2.2 如何同步 Actor 的属性

创建并同步完 Actor 之后,下一步是能够支持 Actor 的数据能够正常同步到客户端,首先在应用层如何支持这一操作?
假设我们有一把武器,需要同步武器的弹药数量,那么需要进行如下定义

/** weapon.h **/
class AWeapon : public  {
	UPROPERTY(replicatedUsing=OnRep_Ammo) // 可选属性,当 Ammo 成功同步后会调用该函数
	int32 Ammo; // 弹药数量
	UFUNCTION()  
	virtual void OnRep_Ammo();
	virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const override; // 属性复制条件控制
}
/** weapon.cpp **/
void AWeapon::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const {
   Super::GetLifetimeReplicatedProps(OutLifetimeProps);  
   DOREPLIFETIME(AWeapon, Ammo); // 具体的复制属性
}

上述定义主要有如下特点:

  • Actor 支持同步时,如果有自定义需要同步的属性,需要重写 GetLifetimeReplicatedProps 函数,并在其中标注要复制的具体属性
  • 同步属性时,可以通过 URPOPERTY 宏中 replicatedUsing 属性来指定同步后要执行的回调函数

2.3 Actor 同步流程

现在我们需要考虑一下,Actor 的属性在什么情况下会被复制?通常来说我们只需要在 Actor 属性被修改时就需要同步到客户端,但是什么时候会被修改我们并不知道,因此引擎中会根据 Actor 复制频率 来做同步检查。参考后文中 4.4 优先级和复制频率 的内容。我们可以梳理出如下流程:

image.png|350

基本上每帧都需要检查有哪些 Actor 需要同步,显然这种检查也是比较耗时的,由此 UE 也引入了 PushModel 技术,手动标记 Actor 哪些属性已修改需要更新,从而节约检查属性的消耗。

2.4 小结

如何创建,同步一个 Actor 的应用层流程基本梳理完毕,但是显然需要知道其后面的原理,由此引出如下问题在后续的文章中解决:

UE

[!warning]
Actor 同步只能从 Server 同步到 Client,Client 唯一向 Server 发送请求的方式只有 RPC,属性同步是单向的

3 RPC 使用分析

3.1 什么是 RPC

RPC(Remote Procedure Call,远程过程调用)是一种用于实现分布式应用程序的技术。通过 RPC,可以使分布式应用程序中的各个部分像本地代码一样交互,即使它们不在同一台计算机或在不同的网络上。
在 RPC 中,一个应用程序可以调用另一个应用程序中的函数或方法,就像调用本地函数一样。这些函数和方法在不同的进程或计算机上执行,但对调用方来说,它们是透明的。调用方不需要了解远程代码的具体实现细节,只需要知道如何调用它们并处理返回值。

[! RPC 的使用有一些前提准则,必须满足这些条件才能调用它]

  1. 它们必须从 Actor 上调用。
  2. Actor 必须被复制。
  3. 如果 RPC 是从服务器调用并在客户端上执行,则只有实际拥有这个 Actor 的客户端才会执行函数。
  4. 如果 RPC 是从客户端调用并在服务器上执行,客户端就必须拥有调用 RPC 的 Actor。
  5. 多播 RPC 则是个例外:
  • 如果它们是从服务器调用,服务器将在本地和所有已连接的客户端上执行它们。
  • 如果它们是从客户端调用,则只在本地而非服务器上执行。
  • 现在,我们有了一个简单的多播事件限制机制:在特定 Actor 的网络更新期内,多播函数将不会复制两次以上。按长期计划,我们会对此进行改善,同时更好的支持跨通道流量管理与限制。

3.2 RPC 的种类

UE 中有 3 种 RPC :

  • Server : 仅在 Server 上调用
  • Client :仅在 Client 上调用
  • NetMulticast :在与服务器连接的所有客户端及服务器本身上调用
    这三种 RPC 只需要在函数调用的声明中加上对应的标记即可。

3.2.1 如何确定 RPC 在哪里被执行

当 RPC 函数在服务器上调用时,有如下情况:


Actor 所有权 未复制 NetMulticast Server Client
Client Owned Actor 在服务器上运行 在服务器和所有客户端上运行 在服务器上运行 在 actor 的所属客户端上运行
Server Owned Actor 在服务器上运行 在服务器和所有客户端上运行 在服务器上运行 在服务器上运行
Unowned Actor 在服务器上运行 在服务器和所有客户端上运行 在服务器上运行 在服务器上运行

当 RPC 函数在客户端上调用时,如下:


Actor 所有权 未复制 NetMulticast Server Client
Owned By Invoking Client 在执行调用的客户端上运行 在执行调用的客户端上运行 在服务器上运行 在执行调用的客户端上运行
Owned By a different client 在执行调用的客户端上运行 在执行调用的客户端上运行 丢弃 在执行调用的客户端上运行
Server Owned Actor 在执行调用的客户端上运行 在执行调用的客户端上运行 丢弃 在执行调用的客户端上运行
Unowned Actor 在执行调用的客户端上运行 在执行调用的客户端上运行 丢弃 在执行调用的客户端上运行

事实上最终判断 RPC 在哪里被执行,主要根据如下三个条件:

  1. 调用端是谁(Client/Server)
  2. 调用的 Actor 属于哪个连接
  3. RPC 的类型(Server/Client/NetMulticast)

举一个例子,有两个客户端 c1 和 c2 各自有 Pawn p1 和 p2,c1 的客户端上能够获取到 p2 这个对象,但是无法利用 p2 调用 RPC,因为在 c1 上 p2 只是一个普通的 Pawn,其没有对应的 c2 的 PlayerController(参考 [[总体框架#3. PlayerController|PlayerController 定义]])。也没有对应的 Connection,因此无法执行 RPC。

[!note]

  1. 实际上是否会调用到对端,主要根据 UObject::GetFunctionCallspace 这个接口返回的枚举来判定的。
  2. 其次根据 Actor 所属的 Connection,如果 Actor 不属于任何一个 Connection(Owner 递归查找找不到 PlayerController),那么也是无法调用 RPC 的。

3.3 RPC 的使用

UE 中,一个 RPC 函数的声明和定义如下(以 Client 调用 Server 执行的 RPC 为例):

/** weapon.h **/
class AWeapon : public  {
	UFUNCTION(Server)
	void Fire();
}

/** weapon.cpp **/
void AWeapon::Fire_Implementation() {
	/** do weapon fire **/ 
}

此时只需要在 Client 端使用如下操作:

AWeapon* Weapon = GetWeapon();
Weapon->Fire();

就能直接调用 Server 端的 Fire 接口了。关于其背后实现的原理,可以参考 [[原理#4. QA#4.5 RPC 函数如何执行的|RPC函数执行原理]]。
这里需要注意一点,UE 的 RPC 是没有返回值的,统一都是 void。个人如果需要获取返回值,那么就需要一个类似协程的概念,来获取返回值,否则只能阻塞等待或者异步等待,后者显然代码可读性也不是很好。

3.4 小结

RPC 与属性同步有些不同,RPC 可以 Server To Client 也可以 Client To Server,是一种双向的通信方式,而属性同步只能 Server To Client,属于单向同步。对于 RPC 的实现,有如下问题可以再进行深究:

UE

4 Actor 同步概念

4.1 NetRole

每个 Actor 都有一个 LocalRole 和 RemoteRole 的概念,分别对应于 Actor 在本地和在对端的 Role,Role 主要分为 3 种:

  • ROLE_SimulatedProxy
  • ROLE_AutonomousProxy
  • ROLE_Authority
    通常 LocalRole=Authority 只存在于服务器(但是客户端也有可能存在,比如 Spawn 一个 Actor 但是不标记为 Replicated)。关于各种 Role 常见的设置可以参考下图:
    image.png|850

4.1.1 AutonomousProxy 和 SimulatedProxy 的区别

  • AutonomousProxy 和 SimulatedProxy 基本只存在于客户端,ROLE_AutonomousProxy 用于处理本地玩家的输入,并将这些输入发送到服务器进行处理,而 ROLE_SimulatedProxy 用于处理其他玩家的输入,并在客户端上模拟 Actor 在服务器上的运行。因此通常 AutonomousProxy 只存在于 PlayerController 和其 Possess 的 Pawn。
  • SimulatedProxy 是标准的模拟途径,通常是根据上次获得的速率对移动进行推算。当服务器为特定的 actor 发送更新时,客户端将向着新的方位调整其位置,然后利用更新的间歇,根据由服务器发送的最近的速率值来继续移动 actor。
  • AutonomousProxy 通常只用于 PlayerController 所拥有的 actor。这说明此 actor 会接收来自真人控制者的输入,所以在我们进行推算时,我们会有更多一些的信息,而且能使用真人输入内容来补足缺失的信息(而不是根据上次获得的速率来进行推算)。

4.1.2 小结

那么这个 Role 有什么用呢?个人认为有如下用处:

  • 在 C/S 模式下,基本可以认为 LocalRole 为 Authority 的 Actor 当前就是处于服务器环境下,用来区分服务器还是客户端
  • 引擎对于 AutonomousProxy 和 SimulatedProxy 做了区分,用来更好的模拟玩家输入

[!note]
就目前而言,只有服务器能够向已连接的客户端同步 Actor (客户端永远都不能向服务器同步)。始终记住这一点, 只有 服务器才能看到 Role == ROLE_Authority 和 RemoteRole == ROLE_SimulatedProxy 或者 ROLE_AutonomousProxy

NetRole

4.2 关联连接

UE 中 Actor关联连接的概念,即这个 Actor 属于哪个连接。在传统的 C/S 服务器中,每个客户端和服务器会有一条连接,在 UE 中会为每个连接创建一个 PlayerController,这样这个 PlayerController 就归这条连接所有。
而如果一个 Actor 的 Owner 为 PlayerController 或者为 Pawn 并且这个 Pawn 拥有一个 PlayerController,那么这个 Actor 就归属于拥有这个 PlayerController 的连接。
这里的关联连接有什么用呢?
考虑如下三种情况:

  • 需要确定哪个客户端将执行运行于客户端的 RPC
  • Actor 复制与连接相关性(比如 bOnlyRelevantToOwner 为 True 的 Actor,只有拥有这个 Actor 的 Connection 才会收到这个 Actor 的属性更新,比如 PlayerController)
  • 涉及 Owner 的 Actor 属性复制条件(比如 COND_OnlyOwner 只能复制给 Owner)

连接所有权

4.3 相关性

相关性是用于判断 Actor 是否需要进行同步的重要依据。其主要判断相关性的接口为 AActor::IsNetRelevantFor 。个人认为相关性最重要的一点是可以有效的节约带宽和同步操作所带来的 CPU 消耗
比如场景的规模可能比较大,玩家特定时刻只能看到关卡中部分 Actor。被服务器认为可见或者能够影响客户端的 Actor 组会被是为该客户端的相关 Actor 组,服务器只会让客户端知道其相关组内的 Actor。

  1. 如果 Actor 是 bAlwaysRelevant、归属于 Pawn 或 PlayerController、本身为 Pawn 或者 Pawn 是某些行为(如噪音或伤害)的发起者,则其具有相关性。
  2. 如果 Actor 是 bNetUseOwnerRelevancy 且拥有一个所有者,则使用所有者的相关性。
  3. 如果 Actor 是 bOnlyRelevantToOwner 且没有通过第一轮检查,则不具有相关性。
  4. 如果 Actor 被附加到另一个 Actor 的骨架模型,它的相关性将取决于其所在基础的相关性。
  5. 如果 Actor 是不可见的 (bHidden == true) 并且它的 Root Component 并没有碰撞,那么则不具有相关性,
    • 如果没有 Root Component 的话,AActor::IsNetRelevantFor() 会记录一条警告,提示是否要将它设置为 bAlwaysRelevant=true
  6. 如果 AGameNetworkManager 被设置为使用基于距离的相关性,则只要 Actor 低于净剔除距离,即被视为具有相关性。

[!note]
Pawn 和 PlayerController 将覆盖 AActor::IsNetRelevantFor() 并最终具有不同的相关性条件。

4.4 优先级和复制频率

4.4.1 优先级

每个 Actor 都有一个名为 NetPriority 的浮点变量。这个变量的数值越大,Actor 相对于其他"同伴"的带宽就越多。和优先级为 1.0 的 Actor 相比,优先级是 2.0 的 Actor 可以得到两倍的更新频度。唯一影响优先顺序的就是它们的比值。
计算 Actor 的当前优先级时使用了函数 AActor::GetNetPriority。为避免出现饥荒(starvation),AActor::GetNetPriority 使用 Actor 上次复制后经过的时间去乘以 NetPriority。同时,GetNetPriority 函数还考虑了 Actor 与观察者的相对位置以及两者之间的距离。

4.4.2 复制频率

Actor 不是每一帧都进行复制的,每个 Actor 有个自己的每秒复制频率 NetUpdateFrequency,每次检查 Tick 的 DeltaTime > 1/NetUpdateFrequency,满足条件才可以进行下一步复制检查。
比如默认 PlayerState 每秒更新 1 次,而 Pawn 每秒更新 100 次(默认情况下服务器 30 fps 运行,基本上每帧都会做复制检查)。

标签:同步,入门,UnrealEngine,Actor,RPC,调用,服务器,客户端
From: https://www.cnblogs.com/lawliet12/p/17306303.html

相关文章

  • git 入门笔记
    Git与Github入门笔记Git版本控制软件引用视频同步笔记:狂神聊Git(qq.com)git小游戏......
  • 金融系统NTP时钟同步(网络校时服务器)架设工作详情
    金融系统NTP时钟同步(网络校时服务器)架设工作详情金融系统NTP时钟同步(网络校时服务器)架设工作详情京准电子科技官微——ahjzsz一、选型思考方面对于NTP时钟服务器设备的选择应该从本单位实际使用情况和市场上设备情况进行综合分析,选取最优方案来,尽量避免非相关因素对设备选型的......
  • selenium驱动未随浏览器更新而同步更新的问题
    基于selenium模拟谷歌浏览器登录时,依赖chromedriver.exe版本信息。但谷歌浏览器升级后,之前创建的脚本可能会出现因驱动版本过低,使得之前创建的脚本运行失败的问题。下面针对该问题进行探索和解决。selenium版本importseleniumselenium.__version__#'4.7.2'获取谷歌浏览......
  • rsync远程同步:下行同步、上行同步+inotify实时同步
    一、rsync远程同步1、什么是rsync远程同步rsync是C/S架构的数据镜像备份工具,可以实现全量备份和快速增量备份支持本地复制或ssh、rsync主机同步。rsync默认端口为873rsync特性:可以在不通主机之间镜像同步整个目录树,支持增量备份、保持链接和权限、时间、属性且传输前自动执行压......
  • 全网最详细中英文ChatGPT-GPT-4示例文档-智能聊天机器人从0到1快速入门——官网推荐的
    目录Introduce简介setting设置Prompt提示Sampleresponse回复样本APIrequest接口请求python接口请求示例node.js接口请求示例curl命令示例json格式示例其它资料下载ChatGPT是目前最先进的AI聊天机器人,它能够理解图片和文字,生成流畅和有趣的回答。如果你想跟上AI时代的潮流......
  • kettle从入门到精通 第十一课 kettle javascript 解析json数组
    1、json步骤虽然可以解析json数组,但是不够灵活。通过javascript步骤来解析json数组比较灵活,且可以按照需要组装数据流转到下个步骤。1)步骤名称:可以自定义2)TransformScripts:当前步骤编写的javascript脚本3)TransformConstants:重新定义的静态常量,用于控制数据行发生的情况。您必......
  • Semantic Kernel 入门系列:
    如果把提示词也算作一种代码的话,那么语义技能所带来的将会是全新编程方式,自然语言编程。通常情况下一段prompt就可以构成一个SemanticFunction,如此这般简单,如果我们提前可以组织好一段段prompt的管理方式,甚至可以不需要写任何的代码,就可以构造出足够多的技能来。使用文件夹管......
  • 斜率优化入门
    前言斜率优化是一种经典的单调队列优化类型,虽然它的名字很高大上,但是其思想内核非常简单,这篇博客就是用来帮助各位快速入门的提示:本博客以单调队列的思想理解斜率优化引入dp优化可以怎么分类?数据结构维护决策点集的插入与查找算法维护决策点集大小,取出无用决策点而......
  • Flask快速入门day 06 (sqlalchemy的使用,scoped-session线程安全)
    目录Flask框架之sqlalchemy的使用一、SQLAlchemy基本使用1、简介2、操作原生sql3、表创建4、ORM操作4、1.基本使用4、2.增删改查4、3.高级查询二、外键关系1、一对多1、1.表模型1、2.新增和基于对象的查询2、多对多2、1.表模型2、2.新增和基于对象查询3、连表查询三、scoped_sessi......
  • dfs入门习题
    主要记录一下个人遇见过的一些dfs的一些入门题目。有需要的可以跟着题单往下做。题单根据自己的刷题不定时更新。 第一题:https://codeforces.com/problemset/problem/510/B一道比较经典的dfs模板题。需要注意一下记忆化搜索。 **点击查看代码......