《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
”,然后新建一个 “控件蓝图” 类(Widget Blueprint) “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_Authority
RemoteRole == ROLE_SimulatedProxy
客户端会将其识别为以下形式:
Role == ROLE_SimulatedProxy
RemoteRole == 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
” 和 “Remote
Role” 值可能发生对调,即如果服务器上有这样的配置 “(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…...
成为git砖家(9): rebase进阶: 拆分commit为多个
问题描述 当一次性 git add 了多个修改点, 并且快速的执行了 git commit 后, 你觉得有点懊恼: 明明可以独立为两次或多次 commit, 揉在一块导致历史记录不太清晰。 比如我在 nn1 这个练手项目中, 最近一次 commit&am…...
pytorch retain_grad vs requires_grad
requires_grad大家都挺熟悉的,因此穿插在retain_grad的例子里进行捎带讲解就行。下面看一个代码片段: import torch# 创建一个标量 tensor,并开启梯度计算 x torch.tensor(2.0, requires_gradTrue)# 中间计算:y 依赖于 x&#x…...
Axure常用变量及使用方法详解
点击下载《Axure常用变量及使用方法详解.pdf》 摘要 Axure RP 作为一款领先的前端原型设计工具,提供了全面的 变量 和 函数 系统,以支持复杂的交互设计和动态内容展示。本文将从专业角度详细解析 Axure 中的 全局变量、中继器数据集变量/函数、元件变量…...
为企业级AI交互系统OpenWebUI集成LDAP用户权限认证(2)
为企业级AI交互系统OpenWebUI集成LDAP用户权限认证(2) 本文介绍如何OpenWebUI系统集成LDAP认证服务,及其用户权限及用户组设置。 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录…...
mac 被禁用docker ui后,如何使用lima虚拟机启动docker
本机macos 安装lima brew install lima创建配置 echo "\\ndynamic:\n big-sur:\n image: docker://docker:git\n linux:\n image: docker.io/limasoftware/ubuntu:20.04 \\n" > ~/.lima/default.yaml启动名叫default的虚拟机 limactl start default进…...
C#实现AES-CBC加密工具类(含完整源码及使用教程)
一、AES-CBC加密应用场景 AES(Advanced Encryption Standard)作为全球公认的安全加密标准,广泛使用在以下场景: API通信加密:保护HTTP接口传输的敏感数据(如身份令牌、支付信息)文件安全存储&…...
Unity辅助工具_头部与svn
Unity调用者按钮增加PlaySideButton using QQu; using UnityEditor; using UnityEngine; [InitializeOnLoad] public class PlaySideButton {static PlaySideButton(){UnityEditorToolbar.RightToolbarGUI.Add(OnRightToolbarGUI);UnityEditorToolbar.LeftToolbarGUI.Add(OnLe…...
VBA 数据库同一表的当前行与其他行的主键重复判断实现方案1
目的,判断是否主键重复,不重复则登录新数据,重复则不登录。 定义类型: DataRecord tableName 表名 rowNumber 行号 columnName 列名 data 数据 想要实现的代码逻辑如下: 模拟数据库的登录过程。假设…...
Pytorch系列教程:可视化Pytorch模型训练过程
深度学习和理解训练过程中的学习和进步机制对于优化性能、诊断欠拟合或过拟合等问题至关重要。将训练过程可视化的过程为学习的动态提供了有价值的见解,使我们能够做出合理的决策。训练进度必须可视化的两种方法是:使用Matplotlib和Tensor Board。在本文…...
CSS伸缩盒模型(弹性盒子)
伸缩盒模型(Flexbox,Flexible Box Layout)是 CSS 中一种一维布局模型,用于更高效地处理元素的对齐、分布和响应式布局。其核心思想是让容器内的子元素(称为“项目”)能够灵活地自动调整大小和位置以适应不同…...
C++蓝桥杯基础篇(十一)
片头 嗨~小伙伴们,大家好!今天我们来学习C蓝桥杯基础篇(十一),学习类,结构体,指针相关知识,准备好了吗?咱们开始咯~ 一、类与结构体 类的定义:在C中&#x…...
版本控制泄露源码 .svn
##相关知识 SVN源码泄露 SVN(subversion)是源代码版本管理软件,造成 SVN 源代码漏洞的主要原因是管理员操作不规范。“ 在使用 SVN 管理本地代码过程中,会自动生成一个名为 .svn 的隐藏文件夹,其中包含重要的源代码信…...
基于单片机的风速报警装置设计
标题:基于单片机的风速报警装置设计 内容:1.摘要 本设计聚焦于基于单片机的风速报警装置,旨在解决传统风速监测缺乏实时报警功能的问题。采用单片机作为核心控制单元,结合风速传感器采集风速数据。经实验测试,该装置能准确测量 0 - 60m/s 范…...
YOLOv12本地部署教程——42%速度提升,让高效目标检测触手可及
YOLOv12 是“你只看一次”(You Only Look Once, YOLO)系列的最新版本,于 2025 年 2 月发布。它引入了注意力机制,提升了检测精度,同时保持了高效的实时性能。在保持速度的同时,显著提升了检测精度。例如&am…...
Banana Pi OpenWRT One Wifi6 OpenWrt社区官方开源路由器评测
第一款不可破解、开源、版权软件、符合 FCC、CE 和 RoHS 的维修权路由器 OpenWRT项目今年已经20岁了,为了纪念这一时刻,Banana Pi OpenWrt One/AP-24.XY路由器开发系统已经上市。这是OpenWRT团队与硬件公司的第一个联合项目。选择 Banana Pi,…...
【算法】经典排序算法介绍+代码示例
排序算法介绍 1)冒泡排序 (Bubble Sort)2)选择排序(Selection Sort)3)插入排序(Insertion Sort)4)希尔排序(Shell Sort)5)归并排序(Me…...
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14_02带边框和斑马纹的固定表头表格
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
【Linux】线程控制
目录 一、原生线程库: 二、线程控制: 1、线程创建: 2、线程等待: 自定义类型的接收对象: 编辑 3、线程终止: pthread_exit: pthread_cancel: 4、线程ID: 线程库的底层原…...
pyqt联合designer的运用和设置
PyQt Designer 简介 PyQt Designer 是一个用于创建和设计 PyQt 应用程序用户界面的可视化工具。它允许用户通过拖放方式添加和排列各种控件,如按钮、文本框、滑块等,并设置它们的属性和样式,从而快速构建出美观且功能完整的 UI 界面。 Windows版本:【免费】安装包别管啊啊…...
spring boot3.4.3+MybatisPlus3.5.5+swagger-ui2.7.0
使用 MyBatis-Plus 操作 books 表。我们将实现以下功能: 创建实体类 Book。 创建 Mapper 接口 BookMapper。 创建 Service 层 BookService 和 BookServiceImpl。 创建 Controller 层 BookController。 配置 MyBatis-Plus 和数据库连接。 1. 项目结构 src ├─…...
利用微软的 HTML 应用程序宿主程序的攻击
mshta.exe 是微软的 HTML 应用程序宿主程序(Microsoft HTML Application Host),属于 Windows 系统组件。它的核心功能是运行 .hta(HTML Application)文件,允许通过 HTML、JavaScript、VBScript 等技术创建交…...
【深度学习】读写文件
读写文件 到目前为止,我们讨论了如何处理数据,以及如何构建、训练和测试深度学习模型。 然而,有时我们希望保存训练的模型,以备将来在各种环境中使用(比如在部署中进行预测)。 此外,当运行一个…...
Bert的使用
一、Data.py # data负责产生两个dataloader from torch.utils.data import DataLoader, Dataset from sklearn.model_selection import train_test_split #给X,Y 和分割比例, 分割出来一个训练集和验证机的X, Y import torchdef read_file(path):data []label …...
Unity使用UGUI制作无限滑动列表
原理参照上一篇使用NGUI的制作无限滑动列表的文章 Unity 使用NGUI制作无限滑动列表_unity 滑动列表很多物体-CSDN博客 准备工作: 新建一个空物体命名为LoopList,并调整其大小, 并增加Scroll Rect组件(用于滑动)、Re…...
ThinkPHP6用户登录系统的全过程
ThinkPHP6用户登录系统的全过程涉及请求处理、数据传输、路由分发、控制器逻辑、模型验证及中间件协作等多个模块的交互。详细的过程解析如下: 1. 前端请求与路由分发 前端发起请求:用户在前端页面(如Vue组件或HTML表单)输入用户…...
C++全栈聊天项目(2) 单例模式封装Http管理者
完善注册类界面 先在注册类构造函数里添加lineEdit的模式为密码模式 ui->lineEdit_Passwd->setEchoMode(QLineEdit::Password); ui->lineEdit_Confirm->setEchoMode(QLineEdit::Password);我们在注册界面的ui里添加一个widget,widget内部包含一个tip居…...
【鸿蒙开发】OpenHarmony调测工具hdc使用教程(设备开发者)
00. 目录 文章目录 00. 目录01. OpenHarmony概述02. hdc简介03. hdc获取04. option相关的命令05. 查询设备列表的命令06. 服务进程相关命令07. 网络相关的命令08. 文件相关的命令09. 应用相关的命令10. 调试相关的命令11. 常见问题12. 附录 01. OpenHarmony概述 OpenHarmony是…...
ORACLE EBS数据库RELINK方式搭建克隆环境
ORACLE EBS系统的数据库,一般都安装了很多特定功能的小补丁来解决特定的BUG;因此对于已经安装好的系统,想要克隆一套测试环境、搭建一个新的备机做测试等,如果按照生产环境标准,则需要安装大量补丁,带来很大…...
MySQL regexp 命令
REGEXP命令是一种用于进行正则表达式匹配的运算符,允许在查询中使用正则表达式来匹配字符串模式1。 基本语法 基本的语法结构如下: SELECT * FROM table_name WHERE column_name REGEXP pattern; 这里,pattern是你要匹配的正则表达式模…...
前端实习到工作的经历
看了很多人的程序员生涯之路,我突然意识到我也该记录一些东西,因此有感而发。 我是一个24届毕业生,大三下就开始找前端实习,当时学校不让走,我们都是先面着然后准备放假就去。当时周围小伙伴都找好了,考完…...
Vue3——Fragment
文章目录 一、Fragment的核心意义1. 解决Vue2的单根限制问题2. 减少不必要的 DOM 嵌套3. 语义化和结构化 二、Fragment 的实现原理三、Fragment 使用方式1. 基本用法2. 结合条件渲染3. 动态组件 四、实际应用场景1. 列表/表格组件2. 布局组件3. 语义化标签 五、注意事项1. 属性…...
Linux_16进程地址空间
CPU内的寄存器只有一套,但是CPU内寄存器的数据可能会有多份! 一、程序地址空间 下面这个图对应的是内存吗?(实际上是虚拟的进程地址空间) 32位机器内存最大为多少? 32位操作系统的地址总线为32位&#x…...
职坐标机器学习编程实战:调试优化与自动化测试精要
内容概要 在机器学习编程实践中,代码调试优化与自动化测试工具的应用是构建高可靠性系统的核心环节。本书聚焦从数据预处理到模型部署的全流程,通过特征工程优化、训练过程监控及持续集成方案的设计,系统化解决算法工程化中的典型问题。在特…...
git文件过大导致gitea仓库镜像推送失败问题解决(push failed: context deadline exceeded)
问题描述: 今天发现gitea仓库推送到某个镜像仓库的操作几个月前已经报错终止推送了,报错如下: 首先翻译报错提示可知是因为git仓库大小超过1G限制。检查本地.git文件,发现.git文件大小已达到1.13G。确定是.git文件过大导致&…...
llvm数据流分析
llvm数据流分析 1.数据流分析2.LLVM实现2.1.常量传播2.2.活跃性分析 相关参考文档:DataFlowAnalysisIntro、ustc编译原理课程、南大程序分析课程1、南大程序分析课程2。 1.数据流分析 数据流分析在编译优化等程序分析任务上都有重要应用。通常数据流分析可被抽象为…...
Vite为什么选用Rollup打包?
Vite 在生产阶段使用 Rollup 打包,但这不是唯一选择。它的设计背后有明确的权衡和考量,同时开发者也可以选择其他替代方案。 一、为什么 Vite 默认使用 Rollup? 1. Rollup 的核心优势 • Tree-shaking:Rollup 的静态分析能力极强&…...
Docker 入门与实战指南
Docker 入门与实战指南 一、Docker 简介 Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖打包成一个可移植的容器。容器可以在任何安装了 Docker 的环境中运行,确保应用的一致性和可移植性。 1.1 为什么使用 Docker? 环境一…...
C# 常用数据类型
C# 数据类型分为 值类型、引用类型 和 特殊类型,以下是详细分类及对应范围/说明: 一、值类型(Value Types) 值类型直接存储数据,分配在栈内存中,默认不可为 null。 简单类型 整数类型…...
深入解读 JavaScript 中 `this` 的指向机制:覆盖所有场景与底层原理
this 是 JavaScript 中最容易引发困惑的核心概念之一,它的指向在不同场景下呈现截然不同的行为。本文将系统性地解析 this 的所有使用场景,结合代码示例和底层原理,帮助你彻底掌握其运行机制。 一、全局环境下的 this 1. 浏览器环境 在浏览器…...
无人机全景应用解析与技术演进趋势
无人机全景应用解析与技术演进趋势 ——从立体安防到万物互联的空中革命 一、现有应用场景全景解析 (一)公共安全领域 1. 立体安防体系 空中哨兵:搭载 77 GHz 77\text{GHz} 77GHz毫米波雷达(探测距离 5 km 5\text{km} 5km&…...
手写简易Tomcat核心实现:深入理解Servlet容器原理
目录 一、Tomcat概况 1. tomcat全局图 2.项目结构概览 二、实现步骤详解 2.1 基础工具包(com.qcby.util) 2.1.1 ResponseUtil:HTTP响应生成工具 2.1.2 SearchClassUtil:类扫描工具 2.1.3 WebServlet:自定义注解…...
【音视频】ffmpeg命令提取像素格式
1、提取YUV数据 提取yuv数据,并保持分辨率与原视频一致 使用-pix_fmt或-pixel_format指定yuv格式提取数据,并保持原来的分辨率 ffmpeg -i music.mp4 -t "01:00" -pixel_format yuv420p music.yuv提取成功后,可以使用ffplay指定y…...
深度剖析Redis:双写一致性问题及解决方案全景解析
在高并发场景下,缓存与数据库的双写一致性是每个开发者必须直面的核心挑战。本文通过5大解决方案,带你彻底攻克这一技术难关! 一、问题全景图:当缓存遇到数据库 1.1 典型问题场景 // 典型问题代码示例 public void updateProduc…...
Redis----大key、热key解决方案、脑裂问题
文章中相关知识点在往期已经更新过了,如果有友友不理解可翻看往期内容 出现脑裂问题怎么保证集群还是高可用的 什么是脑裂问题 脑裂说的就是当我们的主节点没有挂,但是因为网络延迟较大,然后和主节点相连的哨兵通信较差,之后主…...
Android 调用c++报错 exception of type std::bad_alloc: std::bad_alloc
一、报错信息 terminating with uncaught exception of type std::bad_alloc: std::bad_alloc 查了那部分报错c++代码 szGridSize因为文件太大,初始化溢出了 pEGM->pData = new float[szGridSize]; 解决办法 直接抛出异常,文件太大就失败吧 最后还增加一个日志输出,给…...
【从零开始学习计算机科学】操作系统(五)处理器调度
【从零开始学习计算机科学】操作系统(五)处理器调度 处理器调度一些简单的短程调度算法的思路先来先服务(First-Come-First-Served,FCFS)优先级调度及其变种最短作业优先调度算法(SJF)--非抢占式最短作业优先调度算法(SJF)--抢占式最高响应比优先调度算法轮转调度算法…...
LeetCode1871 跳跃游戏VII
LeetCode 跳跃游戏 IV:二进制字符串的跳跃问题 题目描述 给定一个下标从 0 开始的二进制字符串 s 和两个整数 minJump 和 maxJump。初始时,你位于下标 0 处(保证该位置为 0)。你需要判断是否能到达字符串的最后一个位置…...
ResNet50深度解析:原理、结构与PyTorch实现
ResNet50深度解析:原理、结构与PyTorch实现 1. 引言 ResNet(残差网络)是深度学习领域的一项重大突破,它巧妙解决了深层神经网络训练中的梯度消失/爆炸问题,使得构建和训练更深的网络成为可能。作为计算机视觉领域的里…...
MATLAB 控制系统设计与仿真 - 24
PID 控制器分析- 控制器的形式 连续控制器的结构: 为滤波时间常数,这类PID控制器在MATLAB系统控制工具箱称为并联PID控制器,可由MATLAB提供的pid函数直接输入,格式为: 其他类型的控制器也可以由该函数直接输入&#x…...