《UE5_C++多人TPS完整教程》学习笔记34 ——《P35 网络角色(Network Role)》
本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P35 网络角色(Network Role)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。

文章目录
- P35 网络角色(Network Role)
- 35.1 网络角色概念
- 35.2 创建显示网络角色的控件
- 35.3 显示本地网络角色
- 35.4 显示远程网络角色
- 35.5 Summary
P35 网络角色(Network Role)
本节课将讨论虚幻引擎中网络角色(Network role)的概念以及如何在多人游戏中使用它;“网络角色” 包括本地角色(Local role)和远程角色(Remote role),我们将比较两者的不同之处,接着为了更好地掌握(Get a better grasp)网络角色的概念并学以致用,我们将创建一个特殊的部件,它的功能是将玩家控制的游戏人物的当前角色显示在人物头上(Overhead)。

注意:
这里需要避免与虚幻引擎中 “Character” 的中文翻译 “角色” 混淆,以及与 “Actor Role” 进行区分。
在Unreal Engine 4(UE4)中,Network Role 和 Actor Role 是两个相关但不同的概念。
区别:
- Network Role 主要用于描述Actor在网络环境中的角色,决定了Actor在客户端和服务器之间的行为和职责。
- Actor Role 主要用于描述Actor在游戏逻辑中的角色,决定了Actor在游戏中的行为和职责。
联系:
- 在大多数情况下,Network Role 和 Actor Role 是一致的。例如,服务器上的Actor通常同时具有 ROLE_Authority 的 Network Role 和 Actor Role。
- 在客户端上,玩家控制的角色通常同时具有 ROLE_AutonomousProxy 的 Network Role 和 Actor Role。
非玩家控制的角色或其他对象通常同时具有 ROLE_SimulatedProxy 的 Network Role 和 Actor Role。
—— CSDN 《【UE 网络】Network Role and Authority、Actors Owner、Actor Role and RemoteRole》
35.1 网络角色概念
-
在多人游戏中,玩家控制的任何给定人物都有多个版本(Mutiple versions)。如果玩家是连接到服务器的客户端,那么他所控制的游戏人物在自己和其他客户端以及服务器都分别存在一个版本;例如,多人游戏中有两个玩家,那么其中一个玩家所控制的游戏人物在自己的机器上有一个版本,在服务器和另一个玩家的机器上也有一个版本(无法被另一个玩家所控制)。由此可知,如果多人游戏中有三个玩家,那么其中一个玩家将会在其他机器上有三个副本,因此如何区分(Distinguish)我们正在处理的角色属于哪个版本就至关重要了。

-
为了解决这个问题(Sort this problem out),虚幻引擎引入了网络角色的概念以及相应的枚举变量(Enum)“
ENetRole”,它包含几个常用的枚举常量(Enum constant),以供我们识别任何给定的玩家人物的网络角色:- “
ENetRole::ROLE_Authority”:虚幻引擎使用权威服务器模型(Authoritative server model),“ROLE_Authority” 会被分配给(Be assigned to)存在于服务器上的任何人物。 - “
ENetRole::ROLE_SimulatedProxy”:“SimulatedProxy” 可以翻译成 “模拟代理”,顾名思义,它存在于任何不控制当前玩家人物的其他客户端机器上,即当你在自己的机器上控制人物时,你的机器不是服务器,那么 “`ROLE_SimulatedProxy``” 将会分配给你看到其他人物,它们来自服务器和其他客户端,被其他玩家控制。 - “
ENetRole::ROLE_AutonomousProxy”:“AutonomousProxy” 可以翻译成 “自主代理” 存在于可控制当前玩家人物的客户端机器上,即当你在自己的机器上控制人物时,假设你的机器不是服务器,那么“ROLE_AutonomousProxy” 会被分给你的机器;如果你的机器是服务器。 - “
ENetRole::ROLE_None”:分配给没有被定义网络角色的人物。

虚幻引擎使用的默认模型是 服务器授权,意味着服务器对游戏状态固定具有权限,而信息固定从服务器复制到客户端。服务器上的Actor应具有授权的本地角色,而其在远程客户端上的对应Actor应具有模拟或自主代理的本地角色。
-
Authority (权威角色 / 权威端)
Authority Actor,又叫做权威端。指的是服务器上的 Actor。服务器是游戏的权威,负责管理和验证所有的游戏状态和操作。- 服务器控制:Authority Actor在服务器上有完全的控制权,所有的游戏逻辑和状态更新都在服务器上进行。
- 状态验证:服务器会验证客户端发送的请求和操作,确保游戏的公平性和一致性。
- 广播更新:服务器会将更新后的状态广播给所有相关的客户端,确保所有客户端的游戏状态保持一致。
-
Simulated Proxy (模拟代理 / 模拟端)
Simulated Proxy Actor通常用于客户端上的非自主 Actor 。这些 Actor 在客户端上进行模拟,但最终的状态由服务器决定。- 客户端模拟:Simulated Proxy Actor 在客户端上进行模拟,以提供即时的反馈和流畅的游戏体验。
- 服务器同步:尽管客户端进行模拟,最终的状态还是由服务器决定并同步到客户端。
- 减少延迟感:通过在客户端进行模拟,可以减少网络延迟带来的影响,使游戏体验更加流畅。
-
Autonomous Proxy (自主代理 / 主动端)
Autonomous Proxy Actor 通常用于客户端拥有的 Actor ,例如玩家控制的角色(Player Character)。这种 Actor 在客户端上有更多的控制权,并且可以自主地进行一些操作。在UE4的网络架构中,主动端(Autonomous Proxy)主要用于玩家角色,以便直接响应玩家输入并进行本地预测。- 客户端控制:Autonomous Proxy Actor 在客户端上有更多的控制权,允许客户端直接对 Actor 进行输入和操作。
- 本地预测:客户端可以进行本地预测,以减少网络延迟带来的影响。例如,玩家移动时,客户端可以立即显示移动效果,而不必等待服务器的确认。
- 同步到服务器:尽管客户端有更多的控制权,但最终的状态还是需要同步到服务器,服务器会进行验证和纠正。
—— CSDN 《【UE 网络】Network Role and Authority、Actors Owner、Actor Role and RemoteRole》
- “
35.2 创建显示网络角色的控件
-
在虚幻引擎内容浏览器 “C++ 类”(C++ Classes)目录下新建一个 “
UserWidget” C++ 类,命名为 “OverheadWidget”,路径为“.../Blaster/HUD”。


-
在 Visual Studio 中打开头文件 “
OverheadWidget.h”,声明 "UTextBlock类变量 “DisplayText” ,然后进行编译。// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "OverheadWidget.generated.h"/*** */ UCLASS() class BLASTER_API UOverheadWidget : public UUserWidget {GENERATED_BODY()/* P35 网络角色(Network Role)*/public:UPROPERTY((meta = BindWidget)) // 将 C++ 变量 DisplayText 与蓝图部件中的文本块 DisplayText 关联class UTextBlock* DisplayText; // 创建文本块 C++ 类,我们对这个变量的任何更改都会关联到蓝图部件中的文本块/* P35 网络角色(Network Role)*/ }; -
在虚幻引擎的内容浏览器 “
/内容/Blueprints” 目录下新建文件夹 “HUD”,然后新建一个 “控件蓝图” 类 “WBP_OverheadWidget”。



-
双击 “
WBP_OverheadWidget”,进入用户控件设计器窗口。如果在左下 “层级” 面板中有 “画布画板” (Canvas Panel),需要将其删除,接着在 “控制板” 面板中将 “通用” 选项卡下的 “文本”(Text)组件拖拽到设计器中,调整其大小(Resize),重命名为 “DisplayText”,这里需要和头文件 “OverheadWidget.h” 中 "UTextBlock类变量 “DisplayText” 的变量名保持一致;然后在右侧 “细节” 面板的 “字体”(Font)选项卡下设置 “字体样式”(Typeface)为 “常规”(Regular),设置 “对齐”(Justification)为 “居中对齐”(Align center text)

-
在右上方点击 “图表”(Graph)按钮,进入图表编辑模式,在上方工具栏点击 “类设置”(Class Settings),然后在左下方 “细节”(Details)面板中设置 “类选项” 下的 “父类”(Parent Class)为 “
OverheadWidget”。


35.3 显示本地网络角色
- 返回 Visual Studio,打开 “
OverheadWidget.h” 和 “OverheadWidget.cpp”,覆写原生函数 “OnLevelRemovedFromWorld()”,当离开当前关卡或进行关卡转移时将调用此函数移除部件 “OverheadWidget”(注意在 5.1 之后的版本中 “virtual void OnLevelRemoveFromWorld()” 被去除,取而代之的是 “virtual void NativeDestruct()”);接着,声明并定义函数 “SetDisplayText()” 和 “ShowPlayerNetRole()”,用于获取并展示本机玩家网络角色后设置部件 “OverheadWidget” 中文本块 “DisplayText” 显示的文本为玩家的网络角色。/*** OverheadWidget.h ***/// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "OverheadWidget.generated.h"/*** */ UCLASS() class BLASTER_API UOverheadWidget : public UUserWidget {GENERATED_BODY()/* P35 网络角色(Network Role)*/public:UPROPERTY(meta = (BindWidget)) // 将 C++ 变量 DisplayText 与蓝图部件中的文本块 DisplayText 关联class UTextBlock* DisplayText; // 创建文本块 C++ 类,我们对这个变量的任何更改都会关联到蓝图部件中的文本块void SetDisplayText(FString TextToDisplay); // 用于设置并显示文本块的文本UFUNCTION(BlueprintCallable) // 可在蓝图类 BP_Blaster 调用void ShowPlayerNetRole(APawn* InPawn); // 获取并展示本机玩家网络角色protected:virtual void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) override; // 覆写原生函数 OnLevelRemovedFromWorld(),当离开当前关卡或进行关卡转移时将调用此函数移除部件// void OnLevelRemovedFromWorld(): https://docs.unrealengine.com/5.0/en-US/API/Runtime/UMG/Blueprint/UUserWidget/OnLevelRemovedFromWorld/// 在 5.1 之后的版本中 virtual void OnLevelRemoveFromWorld() 被去除,取而代之的是 virtual void NativeDestruct() // void NativeDestruct(): https://docs.unrealengine.com/5.1/en-US/API/Runtime/UMG/Blueprint/UUserWidget/NativeDestruct/// virtual void NativeDestruct() override;/* P35 网络角色(Network Role)*/ };/*** OverheadWidget.cpp ***/// Fill out your copyright notice in the Description page of Project Settings./* P35 网络角色(Network Role)*/ #include "OverheadWidget.h" // 原来自动生成的代码是 #include "HUD/OverheadWidget.h",这里需要把 "GameMode/" 去掉,否则找不到文件 "LobbyGameMode.h" #include "Components/TextBlock.h"void UOverheadWidget::SetDisplayText(FString TextToDisplay) // 设置文本块 DisplayText 显示的文本 { if (DisplayText){DisplayText->SetText(FText::FromString(TextToDisplay)); // 将要展示的文本 TextToDisplay 由虚幻引擎字符流类型 FString 转换为文本类型 FText,并将文本块的文本设置为 TextToDisplay 的内容} }void UOverheadWidget::ShowPlayerNetRole(APawn* InPawn) // 展示本地玩家网络角色 {ENetRole LocalRole = InPawn->GetLocalRole(); // 获取本地玩家网络角色(本地网络角色会因调用它的机器不同)FString Role;switch (LocalRole) // 根据 LocalRole 的值来给 Role 赋值{case ENetRole::ROLE_Authority: // 本地玩家是 Authority (权威角色 / 权威端)Role = FString("Authority");break; // 添加 Break 语句直接退出 switch 分支,后面的 case 语句将不再执行case ENetRole::ROLE_AutonomousProxy: // 本地玩家是 Autonomous Proxy (权威角色 / 权威端)Role = FString("Autonomous Proxy"); break; case ENetRole::ROLE_SimulatedProxy: // 本地玩家是 Simulated Proxy(模拟代理 / 模拟端)Role = FString("Simulated Proxy");break;case ENetRole::ROLE_None: // 本地玩家没有分配网络角色Role = FString("None"); break;default:break;}FString LocalRoleString = FString::Printf(TEXT("Local Role: %s"), *Role); // 打印网络角色以便进行调试SetDisplayText(LocalRoleString); // 设置文本块 DisplayText 显示的文本 }void UOverheadWidget::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) // 当转移关卡时删除控件 {RemoveFromParent(); // 从父类删除实体,用于从场景中删除实体(Removes this entity from its parent. This is used to remove entities from the scene.)// https://dev.epicgames.com/documentation/zh-cn/uefn/verse-api/unrealenginedotcom/temporary/scenegraph/entity/removefromparent?application_version=1.0Super::OnLevelRemovedFromWorld(InLevel, InWorld); // 调用父类的 NativeInitializeAnimation() 函数 } /* void UMenu::NativeDestruct() {MenuTearDown();Super::NativeDestruct(); // 调用父类的 NativeDestruct() 函数 } *//* P35 网络角色(Network Role)*/
-
打开 “
BlasterCharacter.h”,声明头部组件 “OverheadWidget” 为 “ABlasterCharacter” 类的私有成员变量;然后在 “BlasterCharacter.cpp” 的构造函数中创建头部组件对象,然后进行编译。/*** BlasterCharacter.h ***/// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BlasterCharacter.generated.h"UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()public:// Sets default values for this character's propertiesABlasterCharacter();// Called every framevirtual void Tick(float DeltaTime) override;// Called to bind functionality to inputvirtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;// 与轴映射相对应的函数void MoveForward(float Value); // 角色前进或后退void MoveRight(float Value); // 角色左移或右移void Turn(float Value); // 角色视角左转或右转void LookUp(float Value); // 角色俯视或仰视private:UPROPERTY(VisibleAnywhere, Category = Camera) class USpringArmComponent* CameraBoom; // 添加弹簧臂组件,归类为 “Camera”UPROPERTY(VisibleAnywhere, Category = Camera)class UCameraComponent* FollowCamera; // 添加摄像机组件,归类为 “Camera”/* P35 网络角色(Network Role)*/// BlueprintReadOnly:表示该变量只能在蓝图中进行读取操作,不能在蓝图中进行写入操作。常用于定义只读变量。// 我们不能在私有变量中使用关键字 BlueprintReadOnly和 BlueprintReadWrite,除非使用了 meta = (AllowPrivateAccess = "true") 进行指定// UE4中用于定义蓝图变量的元数据(metadata)的所有关键字及其解释和作用可以参见:https://blog.csdn.net/u013007305/article/details/130450354UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) class UWidgetComponent* OverheadWidget; // 添加头部组件/* P35 网络角色(Network Role)*/public: };/*** BlasterCharacter.cpp ***/// Fill out your copyright notice in the Description page of Project Settings.#include "BlasterCharacter.h" #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" #include "GameFramework/CharacterMovementComponent.h"/* P35 网络角色(Network Role)*/ #include "Components/WidgetComponent.h" /* P35 网络角色(Network Role)*/// Sets default values ABlasterCharacter::ABlasterCharacter() {// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.PrimaryActorTick.bCanEverTick = true;// 创建弹簧臂对象 CameraBoom 并设置 CameraBoom 的默认属性CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom")); // 基于弹簧臂组件类创建对象CameraBoom->SetupAttachment(GetMesh()); // 设置弹簧臂附加到角色的骨骼网格体组件,如果附加到胶囊体上,角色在做蹲下的动作时,由于胶囊体的大小和路线会发生改变,弹簧臂的高度也会发生改变(弹簧臂将会移动)CameraBoom->TargetArmLength = 600.f; // 设置弹簧臂长度CameraBoom->bUsePawnControlRotation = true; // 设置弹簧臂跟随角色控制器旋转// 创建摄像机对象 FollowCamera 并设置 FollowCamera 的默认属性FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera")); // 基于摄像机组件类创建对象FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 将摄像机附加到弹簧臂 CameraBoom 上,并指定插槽名为虚幻引擎摄像机组件成员变量 SocketNameFollowCamera->bUsePawnControlRotation = false; // 设置摄像机不跟随角色控制器旋转// 调整弹簧臂和摄像机的相对位置(也可以在虚幻引擎的蓝图编辑器中进行设置)CameraBoom->SetRelativeLocation(FVector(0, 0, 88)); // 设置弹簧臂和摄像机在蓝图类 “BP_BlasterCharacter” 的相对位置为 (0, 0, 88),以避免它们与地面相撞bUseControllerRotationYaw = false; // 设置人物不跟随控制器(镜头)转向,也可以在 BP_BlasterCharacter 蓝图编辑器中实现GetCharacterMovement()->bOrientRotationToMovement = true; // 获取角色移动组件,角色移动时向加速度方向旋转角色,BP_BlasterCharacter 蓝图编辑器中实现/* P35 网络角色(Network Role)*/OverheadWidget = CreateDefaultSubobject<UWidgetComponent> (TEXT("OverheadWidget")); // 基于头部组件类创建对象OverheadWidget->SetupAttachment(RootComponent); // 将头部组件附加到人物根组件 RootComponent 上/* P35 网络角色(Network Role)*/ }... -
在虚幻引擎打开 “
BP_BlasterCharacter” 蓝图编辑器,在左侧 “组件”(Component)面板中可以看到头部组件 “OverheadWidget” 已经附加到 “胶囊体组件 (CollisionCylinder)”(Capsule Component(CollisionCylinder)) ,点击它,并在右侧 “细节”(Details)面板 “用户界面”(USER INTERFACE)选项卡下将 “空间”(Space)从 “世界”(World) 设置为 “屏幕”(Screen),将 “控件类”(Widget Class)设置为 “WBP_OverheadWidget”,勾选 “以所需大小绘制”(Draw at Desired Size),这样我们就不必手动设置(Manually set)这个部件的大小。
-
在左侧 “组件”(Component)面板中将 “
OverheadWidget” 拖拽至 “事件图表”(Event Graph),然后按照下图连接蓝图节点,这段蓝图程序实现了在玩家角色的头顶显示网络角色的功能:节点 “获取用户控件对象”(Get User Widget Object)用于获取当前角色上的 “OverheadWidget” 组件,返回的是一个 “用户控件” (User Widget)类型的值;节点 “类型转换为WBP_OverheadWidget”(Cast To WBP_OverheadWidget)将 “用户控件” (User Widget)类型转换为 “WBP_OverheadWidget” 类型,成功转换后,就可以调用 “WBP_OverheadWidget” 内部的函数 “Show PlayerNetRole()”;这个函数的 “目标”(Target) 是OverheadWidget,输入参数 “In Pawn” 传入 “Self”(当前角色 “BlasterCharacter”)。

-
点击上方 “视口”(Viewport) 选项卡,接着在左侧 “组件”(Component)面板中点击 “
OverheadWidget”,然后在 “视口” 中将该组件拖拽移动至人物头顶。

-
打开关卡 “
BlasterMap”,在工具栏点击 “ ⋮ \vdots ⋮”,这是 “修改游戏模式和游戏设置”(Change Play Mode and Play Settings) 的按钮, 修改 “玩家数量”(Number of Players) 为 3,“网络模式”(Net Mode)为 “以监听服务器”(Play as Listen Server),当我们进行测试时,其中一个玩家将作为监听服务器,而其他两个玩家为客户端。

-
点击工具栏的 “播放”(▶)按钮启动运行,可以看到视口面板中的玩家是监听服务器,弹出的两个窗口为客户端。视口面板中显示三个玩家的 本地 网络角色都是 “Authority”,因为在服务器上的人物都具有 本地 “Authority” 角色(权威角色 / 权威端);而能在其中一个客户端被控制的那个人物(下图红圈标注)具有 本地 “Autonomous Proxy” 角色(自主代理 / 主动端),其他不能被控制的人物具有 本地 “Simulated Proxy” 角色(模拟代理 / 模拟端)。但是,我们无法仅从本地角色中分辨出哪个人物是由服务器控制的,下面我们将尝试显示人物的 远程 网络角色。

35.4 显示远程网络角色
- 返回 Visual Studio,打开 “
OverheadWidget.cpp”,将函数 “ShowPlayerNetRole()” 中的变量名 “LocalRole” 改为 “RemoteRole”。/*** OverheadWidget.cpp ***/...void UOverheadWidget::ShowPlayerNetRole(APawn* InPawn) // 展示本地玩家网络角色 {// ENetRole LocalRole = InPawn->GetLocalRole(); // 获取本地玩家网络角色(本地网络角色会因调用它的机器不同)ENetRole RemoteRole = InPawn->GetRemoteRole(); // 获取远程玩家网络角色FString Role;/*switch (LocalRole) // 根据 LocalRole 的值来给 Role 赋值{case ENetRole::ROLE_Authority: // 本地玩家是 Authority (权威角色 / 权威端)Role = FString("Authority");break; // 添加 Break 语句直接退出 switch 分支,后面的 case 语句将不再执行case ENetRole::ROLE_AutonomousProxy: // 本地玩家是 Autonomous Proxy (权威角色 / 权威端)Role = FString("Autonomous Proxy"); break; case ENetRole::ROLE_SimulatedProxy: // 本地玩家是 Simulated Proxy(模拟代理 / 模拟端)Role = FString("Simulated Proxy");break;case ENetRole::ROLE_None: // 本地玩家没有分配网络角色Role = FString("None"); break;default:break;}*/switch (RemoteRole) // 根据 RemoteRole 的值来给 Role 赋值{case ENetRole::ROLE_Authority: // 远程玩家是 Authority (权威角色 / 权威端)Role = FString("Authority");break; // 添加 Break 语句直接退出 switch 分支,后面的 case 语句将不再执行case ENetRole::ROLE_AutonomousProxy: // 远程玩家是 Autonomous Proxy (权威角色 / 权威端)Role = FString("Autonomous Proxy");break;case ENetRole::ROLE_SimulatedProxy: // 远程玩家是 Simulated Proxy(模拟代理 / 模拟端)Role = FString("Simulated Proxy");break;case ENetRole::ROLE_None: // 远程玩家没有分配网络角色Role = FString("None");break;default:break;}// FString LocalRoleString = FString::Printf(TEXT("Local Role: %s"), *Role); // 打印网络角色以便进行调试// SetDisplayText(LocalRoleString); // 设置文本块 DisplayText 显示的文本FString RemoteRoleString = FString::Printf(TEXT("Remote Role: %s"), *Role); // 打印网络角色以便进行调试SetDisplayText(RemoteRoleString); // 设置文本块 DisplayText 显示的文本 }... - 点击工具栏的 “播放”(▶)按钮启动运行,可以看到视口面板中的玩家是监听服务器,弹出的两个窗口为客户端。视口面板中能被控制的那个人物(下图红圈标注)具有 远程 “Autonomous Proxy” 角色(自主代理 / 主动端),其他不能被控制的人物具有 远程 “Simulated Proxy” 角色(模拟代理 / 模拟端);而在客户端中,所有人物无论可不可以被控制,远程 网络角色都是 “Authority”。由此我们就可以根据人物 本地 和 远程 网络角色来判断玩家的机器属于服务器端还是客户端。
在 Actor 的复制过程中,有两个属性扮演了重要角色,分别是 Role 和 RemoteRole。
有了这两个属性,您可以知道:- 谁拥有 actor 的主控权
- actor 是否被复制
- 复制模式
首先一件要确定的事,就是谁拥有特定 actor 的主控权。要确定当前运行的引擎实例是否有主控者,需要查看 Role 属性是否为
ROLE_Authority。如果是,就表明这个运行中的 虚幻引擎 实例负责掌管此 actor(决定其是否被复制)。
如果 Role 是ROLE_Authority,RemoteRole 是ROLE_SimulatedProxy或ROLE_AutonomousProxy,就说明这个引擎实例负责将此 actor 复制到远程连接。就目前而言,只有服务器能够向已连接的客户端同步 Actor (客户端永远都不能向服务器同步)。始终记住这一点, 只有 服务器 才能看到
Role == ROLE_Authority和RemoteRole == ROLE_SimulatedProxy或者ROLE_AutonomousProxy。
Role/RemoteRole 对调
对于不同的数值观察者,它们的 Role 和 RemoteRole 值可能发生对调。例如,如果您的服务器上有这样的配置:Role == ROLE_AuthorityRemoteRole == ROLE_SimulatedProxy
客户端会将其识别为以下形式:
Role == ROLE_SimulatedProxyRemoteRole == ROLE_Authority
这种情况是正常的,因为服务器要负责掌管 actor 并将其复制到客户端。而客户端只是接收更新,并在更新的间歇模拟 actor。
复制模式
服务器不会在每次更新时复制 actor。这会消耗太多的带宽和 CPU 资源。实际上,服务器会按照AActor::NetUpdateFrequency属性指定的频度来复制 actor。
因此在 actor 更新的间歇,会有一些时间数据被传递到客户端。这会导致 actor 呈现出断续、不连贯的移动。为了弥补这个缺陷,客户端将在更新的间歇中模拟 actor。
目前共有两种类型的模拟。ROLE_SimulatedProxy
这是标准的模拟途径,通常是根据上次获得的速率对移动进行推算。当服务器为特定的 actor 发送更新时,客户端将向着新的方位调整其位置,然后利用更新的间歇,根据由服务器发送的最近的速率值来继续移动 actor。
使用上次获得的速率值进行模拟,只是普通模拟方式中的一种。您完全可以编写自己的定制代码,在服务器更新的间隔使用其他的一些信息来进行推算。ROLE_AutonomousProxy
这种模拟通常只用于 PlayerController 所拥有的 actor。这说明此 actor 会接收来自真人控制者的输入,所以在我们进行推算时,我们会有更多一些的信息,而且能使用真人输入内容来补足缺失的信息(而不是根据上次获得的速率来进行推算)。
虚幻引擎官方文档 《Actor 的 Role 和 RemoteRole 属性》
35.5 Summary
本节课围绕虚幻引擎的网络角色展开,在多人游戏中,玩家控制的任何给定人物都有多个版本,如果多人游戏中有三个玩家,那么其中一个玩家将会在其他机器上有三个副本,为了解决如何区分我们正在处理的角色属于哪个版本,虚幻引擎引入了网络角色的概念以及相应的枚举变量“ENetRole”,它包含 “ENetRole::ROLE_Authority”“ENetRole::ROLE_SimulatedProxy”、“ENetRole::ROLE_AutonomousProxy”:以及 “ENetRole::ROLE_None” 四个常用的枚举常量。为了查看玩家人物在监听服务器和客户端上的本地网络角色和远程网络角色,我们创建 “OverheadWidget” 控件类,绑定 “UTextBlock” 文本组件,在玩家人物的蓝图类 “BlasterCharacter” 中将控件 “OverheadWidget” 附加至角色头顶,这样就可以动态显示网络角色。最后我们进行了多端测试验证,以监听服务器模式启动多玩家实例,进一步理解本地网络角色和远程网络角色在服务端与客户端的显示逻辑。

在 35.3 显示本地网路角色 和 35.4 显示远程网路角色 中,我们在进行测试时,可以看到两种情况下监听服务器端和客户端出现了 本地网络角色和远程网络角色对调 的现象,对于不同的数值观察者,它们的 “(Local)Role” 和 “RemoteRole” 值可能发生对调,即如果服务器上有这样的配置 “(Local)Role == ROLE_Authority” 以及“RemoteRole == ROLE_SimulatedProxy”,客户端会将其识别为 “(Local)Role == ROLE_SimulatedProxy” 以及 “RemoteRole == ROLE_Authority”,这种情况是正常的,因为服务器要负责掌管 Actor 并将其复制到客户端。而客户端只是接收更新,并在更新的间歇模拟 Actor。
相关文章:
《UE5_C++多人TPS完整教程》学习笔记34 ——《P35 网络角色(Network Role)》
本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P35 网络角色(Network Role)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephe…...
手写简易Tomcat核心实现:深入理解Servlet容器原理
目录 一、Tomcat概况 1. tomcat全局图 2.项目结构概览 二、实现步骤详解 2.1 基础工具包(com.qcby.util) 2.1.1 ResponseUtil:HTTP响应生成工具 2.1.2 SearchClassUtil:类扫描工具 2.1.3 WebServlet:自定义注解…...
ES Filter Query 区别
在 Elasticsearch(ES) 中,Filter 和 Query 是两种常用的数据检索方式,它们的主要区别在于 是否计算相关性分数(Score) 以及 是否使用缓存。以下是它们的详细区别和应用场景: 1. 核心区别 特性F…...
Java多线程与高并发专题——关于CopyOnWrite 容器特点
引入 在 CopyOnWriteArrayList 出现之前,我们已经有了 ArrayList 和 LinkedList 作为 List 的数组和链表的实现,而且也有了线程安全的 Vector 和Collections.synchronizedList() 可以使用。 首先我们来看看Vector是如何实现线程安全的 ,还是…...
春节面对大流量并发,系统该如何设计
基于6个层次进行系统设计以解决大流量瞬时并发问题:CDN层,Nginx层,服务层,缓存层,数据库层,全链路压测监控。以下为具体设计参考 1. CDN(内容分发网络) 作用:静态资源&…...
mac本地安装运行Redis-单机
记录一下我以前用的连接服务器的跨平台SSH客户端。 因为还要准备毕设...... 服务器又过期了,只能把redis安装下载到本地了。 目录 1.github下载Redis 2.安装homebrew 3.更新GCC 4.自行安装Redis 5.通过 Homebrew 安装 Redis 安装地址:https://git…...
77.ObservableCollection使用介绍1 C#例子 WPF例子
可观察集合ObservableCollection using System; using System.Collections.ObjectModel;class Program {static void Main(){// 创建一个可观察集合ObservableCollection<string> list new ObservableCollection<string>();// 注册集合变化事件list.CollectionCh…...
【ThreeJS Basics 09】Debug
文章目录 简介从 dat.GUI 到 lil-gui例子安装 lil-gui 并实例化不同类型的调整改变位置针对非属性的调整复选框颜色 功能/按钮调整几何形状文件夹调整 GUI宽度标题关闭文件夹隐藏按键切换 结论 简介 每一个创意项目的一个基本方面是能够轻松调整。开发人员和参与项目的其他参与…...
在 k8s中查看最大 CPU 和内存的极限
在 Kubernetes(k8s)中,你可以从不同层面查看最大 CPU 和内存的极限,下面为你详细介绍从节点和集群层面查看的方法。 查看节点的 CPU 和内存极限 节点的 CPU 和内存极限是指单个节点上可分配的最大资源量,可通过以下几…...
【笔记】STM32L4系列使用RT-Thread Studio电源管理组件(PM框架)实现低功耗
硬件平台:STM32L431RCT6 RT-Thread版本:4.1.0 目录 一.新建工程 二.配置工程 编辑 三.移植pm驱动 四.配置cubeMX 五.修改驱动文件,干掉报错 六.增加用户低功耗逻辑 1.设置唤醒方式 2.设置睡眠时以及唤醒后动作 编辑 3.增加测试命…...
类和对象:
1. 类的定义: 1. 类定义格式: 对于我们的类的话,我们是把类看成一个整体,我们的函数里面没有找到我们的成员变量,我们就在我们的类里面找。 我们看我们的第二点: 我们的类里面,我们通常会对…...
【十三】Golang 通道
💢欢迎来到张胤尘的开源技术站 💥开源如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 通道通道声明初始化缓冲机制无缓冲通道代码示例 带…...
对接RAGflow的API接口报错
对接RAGflow的API接口,报错: {"status":"success","message":"API连接正常","response":{"code":109,"data":false,"message":"Authentication error: API key …...
软考中级_【软件设计师】知识点之【面向对象】
简介: 软件设计师考试中,面向对象模块为核心考点,涵盖类与对象、继承、封装、多态等基础概念,重点考查UML建模(类图/时序图/用例图)、设计模式(如工厂、单例模式)及SOLID设计原则。要…...
Excel中COUNTIF用法解析
COUNTIF 是 Excel 中一个非常实用的函数,用于统计满足某个条件的单元格数量。它的基本语法如下: 基本语法 COUNTIF(范围, 条件) 范围:需要统计的单元格区域,例如 A1:A10 或整列 A:A。 条件:用于判断哪些单元格需要被…...
分布式锁—7.Curator的分布式锁一
大纲 1.Curator的可重入锁的源码 2.Curator的非可重入锁的源码 3.Curator的可重入读写锁的源码 4.Curator的MultiLock源码 5.Curator的Semaphore源码 1.Curator的可重入锁的源码 (1)InterProcessMutex获取分布式锁 (2)InterProcessMutex的初始化 (3)InterProcessMutex.…...
《UE5_C++多人TPS完整教程》学习笔记35 ——《P36 武器类(Weapon Class)》
本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P36 武器类(Weapon Class)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephen …...
【SpringMVC】SpringMVC的启动过程与原理分析:从源码到实战
SpringMVC的启动过程与原理分析:从源码到实战 SpringMVC是Spring框架中用于构建Web应用的核心模块,它基于MVC(Model-View-Controller)设计模式,提供了灵活且强大的Web开发能力。本文将深入分析SpringMVC的启动过程、核…...
出现“ping不通但可以远程连接”的情况可能由以下原因导致
出现“ping不通但可以远程连接”的情况可能由以下原因导致: 1.防火墙或安全软件限制 • 原因:防火墙或安全软件可能阻止了ICMP数据包(ping使用的协议),但允许了远程连接所需的协议(如TCP)。 …...
MySQL表空间碎片原理和解决方案
一、表空间与碎片的基本概念 表空间:MySQL中存储表数据和索引的物理文件(如InnoDB的.ibd文件)。分为系统表空间和独立表空间。碎片:数据在物理存储上不连续,分为行级碎片(单行跨多页)和页级碎片…...
[密码学实战]Java实现国密TLSv1.3单向认证
一、代码运行结果 1.1 运行环境 1.2 运行结果 1.3 项目架构 二、TLS 协议基础与国密背景 2.1 TLS 协议的核心作用 TLS(Transport Layer Security) 是保障网络通信安全的加密协议,位于 TCP/IP 协议栈的应用层和传输层之间,提供: • 数据机密性:通过对称加密算法(如 AE…...
最小栈 _ _
一:题目 二:思路 解释:一个栈名为st,其用来正常的出入栈,一个栈名为minst,其的栈顶元素一定是最小的元素 入栈:第一个元素,两个栈一起入,后面再入栈,只有入栈…...
HTTPS加密原理详解
目录 HTTPS是什么 加密是什么 HTTPS的工作流程 1.使用对称加密 2.引入非对称加密 3.引入证书机制 客户端验证证书真伪的过程 签名的加密流程 整体工作流程 总结 HTTPS是什么 HTTPS协议也是一个应用程协议,是在HTTP的基础上加入了一个加密层,由…...
黑金风格人像静物户外旅拍Lr调色教程,手机滤镜PS+Lightroom预设下载!
调色教程 针对人像、静物以及户外旅拍照片,运用 Lightroom 软件进行风格化调色工作。旨在通过软件中的多种工具,如基本参数调整、HSL(色相、饱和度、明亮度)调整、曲线工具等改变照片原本的色彩、明度、对比度等属性,将…...
安装pyqt6出现的问题
安装PyQt6报错: PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: C:\\Users\\xyj19\\AppData\\Local\\Temp\\tmp3xfmekh7 [end of output] note: This error originates from a subprocess, and is likely not a pr…...
java调用c++
VScode 配置java 并且使用JNA调用c 动态库 安装 Java 开发环境 安装 JDK官网直接下载就好,推荐镜像下载 通过网盘分享的文件:jdk-8u144-windows-x64.exe 链接: https://pan.baidu.com/s/1Ov9bJkPNnOgcliBL-PSTFQ?pwdpg43 提取码: pg43 直接安…...
gitlab+jenkins+harbor+k8s安装操作流程之Jenkins
准备环境 一台centos7系统 4C/8G/100G 如果是jenkins2.5以上版本需要centos8以上版本 JDK1.8编译安装(最新版本jdk需要18以上) MAVEN编译安装 GIT编译安装 JDK1.8步骤 tar -zxvf 解压 vim /etc/profile export JAVA_HOME/data/jdk1.8.0_111 export JRE_HOME$JAVA…...
【机械视觉】C#+VisionPro联合编程———【三、加载CogToolBlock工具详解,以及实例】
【机械视觉】C#VisionPro联合编程———【三、加载CogToolBlock工具详解,以及实例】 在VisionPro中,CogToolBlock 是一种容器工具,可以将多个视觉工具(如CogBlob、CogPMAlign等)组合成一个可复用的流程。通过C#与Visi…...
Android14 串口控制是能wifi adb实现简介
Android14 串口控制是能wifi adb实现简介 一、前言 文章目录 Android14 串口控制是能wifi adb实现简介一、前言二、Android14 串口控制是能wifi adb实现1、设置prop属性命令开启adb(1)相关prop属性设置(2)在设置界面或者 ifconfi…...
启动wsl里的Ubuntu24报错:当前计算机配置不支持 WSL2,HCS_E_HYPERV_NOT_INSTALLED
问题:启动wsl里的Ubuntu24报错 报错信息: 当前计算机配置不支持 WSL2。 请启用“虚拟机平台”可选组件,并确保在 BIOS 中启用虚拟化。 通过运行以下命令启用“虚拟机平台”: wsl.exe --install --no-distribution 有关信息,请访…...
