UnrealEngine - 网络同步入门

news2024/11/15 11:33:00

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 中网络同步的整体框架是如何驱动的?
  • UE 中如何利用这个网络同步的框架同步一个 Actor?其从服务端执行 SpawnActor 开始到客户端接收到请求并创建出 Actor 的流程及调用堆栈是怎样的?
  • 属性同步中 replicatedUsing 是如何实现的?这个宏是如何作用到属性上的?
  • 如何检查属性是否修改?如何对比?

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 所有权未复制NetMulticastServerClient
Client Owned Actor在服务器上运行在服务器和所有客户端上运行在服务器上运行在 actor 的所属客户端上运行
Server Owned Actor在服务器上运行在服务器和所有客户端上运行在服务器上运行在服务器上运行
Unowned Actor在服务器上运行在服务器和所有客户端上运行在服务器上运行在服务器上运行

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


Actor 所有权未复制NetMulticastServerClient
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。

  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 的实现,有如下问题可以再进行深究:

  • 如何调用对端的函数?RPC 的前提是利用 Actor 去调用,Actor 本身存有反射信息,因此问题简化为,告诉对端哪一个 Actor 要执行名称为 xxx 的函数
  • 函数的参数如何封装好并传递到对端?
  • NetMulticast 如何转发函数?

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 做了区分,用来更好的模拟玩家输入

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

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 低于净剔除距离,即被视为具有相关性。

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 运行,基本上每帧都会做复制检查)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/834857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

区块链实验室(14) - 编译FISCO-BCOS

FISCO-BCOS是一种区块链平台&#xff0c;与Hyperledger和Ethereum有些不同&#xff0c;详见FISCO BCOS 区块链 编译FISCO BCOS源码的目的是修改或者新增其中功能模块&#xff0c;进行对比实验&#xff0c;验证新想法、新创意的效果。编译的步骤很简单&#xff0c;按技术文档一…

postman----传参格式(json格式、表单格式)

本文主要讲解postman使用post请求方法的2中传参方式&#xff1a;json格式、表单格式 首先了解下&#xff0c;postman进行接口测试&#xff0c;必须条件是&#xff1a; ♥请求地址 ♥请求协议 ♥请求方式 ♥请求头 ♥参数 json格式 先看一下接口文档&#xff0c;根据接口文档&…

算法通关村——如何使用中序和后序来恢复一棵二叉树

通过序列构造二叉树 给出以下三个二叉树遍历的序列&#xff1a; (1) 前序: 1 2 3 4 5 6 8 7 9 10 11 12 13 15 14 (2) 中序: 3 4 8 6 7 5 2 1 10 9 11 15 13 14 12 (3) 后序: 8 7 6 5 4 3 2 10 15 14 13 12 11 9 1 前中序复原二叉树 所需序列 (1) 前序: 1 2 3 4 5 6 8 7 9 10 …

UG\NX二次开发 属性更新的注意事项 UF_ATTR_assign

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan 简介: 将属性值连接到表达式。如果原始部件中已经存在同名的常量值属性,则会导致属性连接到表达式失败,并且不返回错误值。如果原始部件同名的属性不存在,或者存在同名的连接表达式的属性,则会…

Django之JWT库与SimpleJWT库的使用

Django之JWT库与SimpleJWT库的使用 JWTJWT概述头部(header)载荷(payload)签名(signature) Django使用JWT说明jwt库的使用安装依赖库配置settings.py文件配置urls.py文件创建视图配置权限 SimpleJWT库的使用安装SimpleJWT库配置Django项目配置路由创建用户接口测试身份认证自定义…

小狐狸GPT付费创作系统WEB版源码-登录权限验证逻辑

小狐狸GPT付费创作系统WEB版默认是需要公众号关注登录&#xff0c;一直想改成账号密码登录形式&#xff0c;继续查看接口部分 获取系统设置信息的接口 /web.php/login/system 从header里取x-site作为sitecode&#xff0c;如果取不到默认1 从setting表里查出设置数据&#xff0c…

【动态规划part17】| 647.回文子串、516.最长回文子序列

&#x1f388;LeetCode647.回文子串 链接&#xff1a;647.回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置…

C++初阶函数重载

目录 函数重载函数名修饰规则 函数重载 C语言不允许同名函数 CPP可以&#xff0c;但是要求构成函数重载 函数名相同&#xff0c;参数不同(参数类型、参数个数、参数类型的顺序)&#xff0c;返回值不同不能构成重载 int Add(int left, int right) {cout << "int Ad…

【JS交互篇】BOM基础、Window、Location、Navagator、Screen、History对象

一、BOM 概述 在 JavaScript 语言中有三种对象&#xff1a;内置对象、宿主对象、自定义对象。 宿主对象就是执行 JavaScript 脚本的环境所提供的对象。对于网页编程来说&#xff0c;js 是运行在浏览器上的&#xff0c;所以对于网页编程来说&#xff0c;宿主对象就是浏览器对象…

“我要找到你”——主动救助,让乡村重疾儿童看病不再“两眼一抹黑”

走出医院&#xff0c;主动寻找 郑小伟是河北省阜平县妇幼保健院的儿科主任&#xff0c;在这里工作快20年了。从2021年11月开始&#xff0c;她的桌上一直摆着厚厚一叠阜平县重疾儿童住院名单。她对着上面的名字一个个打电话&#xff0c;手机打不通&#xff0c;就换座机再接着打…

高级语言相关理论[编译VS解释,动态VS静态,强类型VS弱类型]

编译型语言 VS 解释型语言 计算机高级语言按程序执行方式分为编译型和解释型 编译型和解释型语言的执行流程 编译型语言 所有源代码一次性通过编译器转换成二进制指令,即生成一个可执行程序(如Windows下的.exe),可执行程序包含的就是机器码;且无需重新编译,实现一次编译,无限…

Grafana集成prometheus(1.Prometheus安装)

下载docker镜像 docker pull prom/prometheus docker pull prom/node-exporter启动 node-exporter 该程序用以采集机器内存等数据 启动脚本 docker run -d -p 9100:9100 prom/node-exporter ss -anptl | grep 9100启动截图 prometheus 启动脚本 # 3b907f5313b7 为镜像i…

工业电子中的安森美推出PWM控制器 NCP1252ADR2G 用于正向和反激应用

NCP1252ADR2G 深力科 控制器提供了构建专用于 ATX 电源和任何正向应用的成本高效且可靠的 AC-DC 开关电源所需的一切内容。由于使用内部固定的计时器&#xff0c;可以在不依赖辅助 Vcc 的情况下检测输出过载。欠压输入针对低输入电压提供保护&#xff0c;提高了转换器安全性。最…

c语言——杨辉三角

//杨辉三角 #include<stdio.h> int main() {int i,j,k,n0,a[10][10];while(n<0||n>13){/*行数不超过13&#xff0c;为了显示规范*/printf("n即输入行数");scanf("%d",&n);}printf("%d行杨辉三角如下&#xff1a;\n",n);for(i1;i…

迁移学习:使用Restnet预训练模型构建高效的水果识别模型

目录 引言 1 迁移学习 1.1 什么是迁移学习 1.2 迁移学习能解决什么问题 1.3 迁移学习面临的三个问题 1.3.1 何时迁移 1.3.2 何处迁移 1.3.3 如何迁移 1.4 迁移学习的分类 1.4.1 按照学习方式的划分 1.4.2 按照使用方法的划分 2 Restnet网络 2.1 Restnet介绍 2.2 Re…

【FAQ】EasyGBS平台通道显示在线,视频无法播放并报错400的排查

EasyGBS是基于国标GB28181协议的视频云服务平台&#xff0c;它可以支持国标协议的设备接入&#xff0c;在视频能力上能实现直播、录像存储、检索与回放、云台控制、告警上报、语音对讲、平台级联等功能&#xff0c;既能作为业务平台使用&#xff0c;也能作为能力层平台调用。 我…

sentinel组件

目录 定义 4.加SentinelResource,blockHander是超过阈值之后执行的函数 5.设置阈值 6.springboot集成sentinel 定义 1.sentinel知道当前流量大小&#xff0c;在浏览器和后端之间加sentinel控制流量&#xff0c;避免大批量的瞬时请求都达到服务上&#xff0c;将服务压垮 2.…

数学建模-爬虫系统学习

尚硅谷Python爬虫教程小白零基础速通&#xff08;含python基础爬虫案例&#xff09; 内容包括&#xff1a;Python基础、Urllib、解析&#xff08;xpath、jsonpath、beautiful&#xff09;、requests、selenium、Scrapy框架 python基础 进阶&#xff08;字符串 列表 元组 字典…

Ceph入门到精通-远程开发Windows下使用SSH密钥实现免密登陆Linux服务器

工具&#xff1a; win10、WinSCP 服务器生成ssh密钥&#xff1a; 打开终端&#xff0c;使账号密码登录&#xff0c;输入命令 ssh-keygen -t rsa Winscp下载 Downloading WinSCP-6.1.1-Setup.exe :: WinSCP window 生成密钥 打开powershell ssh-keygen -t rsa 注意路径 …

招投标系统简介 企业电子招投标采购系统源码之电子招投标系统 —降低企业采购成本 tbms

​功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…