《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 网络角色概念

  1. 在多人游戏中,玩家控制的任何给定人物都有多个版本(Mutiple versions)。如果玩家是连接到服务器的客户端,那么他所控制的游戏人物在自己和其他客户端以及服务器都分别存在一个版本;例如,多人游戏中有两个玩家,那么其中一个玩家所控制的游戏人物在自己的机器上有一个版本,在服务器和另一个玩家的机器上也有一个版本(无法被另一个玩家所控制)。由此可知,如果多人游戏中有三个玩家,那么其中一个玩家将会在其他机器上有三个副本,因此如何区分(Distinguish)我们正在处理的角色属于哪个版本就至关重要了。
    在这里插入图片描述

  2. 为了解决这个问题(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应具有模拟或自主代理的本地角色。

    1. Authority (权威角色 / 权威端)
      Authority Actor,又叫做权威端。指的是服务器上的 Actor。服务器是游戏的权威,负责管理和验证所有的游戏状态和操作。

      • 服务器控制:Authority Actor在服务器上有完全的控制权,所有的游戏逻辑和状态更新都在服务器上进行。
      • 状态验证:服务器会验证客户端发送的请求和操作,确保游戏的公平性和一致性。
      • 广播更新:服务器会将更新后的状态广播给所有相关的客户端,确保所有客户端的游戏状态保持一致。
    2. Simulated Proxy (模拟代理 / 模拟端)
      Simulated Proxy Actor通常用于客户端上的非自主 Actor 。这些 Actor 在客户端上进行模拟,但最终的状态由服务器决定。

      • 客户端模拟:Simulated Proxy Actor 在客户端上进行模拟,以提供即时的反馈和流畅的游戏体验。
      • 服务器同步:尽管客户端进行模拟,最终的状态还是由服务器决定并同步到客户端。
      • 减少延迟感:通过在客户端进行模拟,可以减少网络延迟带来的影响,使游戏体验更加流畅。
    3. 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 创建显示网络角色的控件

  1. 在虚幻引擎内容浏览器 “C++ 类”(C++ Classes)目录下新建一个 “UserWidget” C++ 类,命名为 “OverheadWidget”,路径为“.../Blaster/HUD”。
    在这里插入图片描述
    在这里插入图片描述

  2. 在 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)*/
    };
  3. 在虚幻引擎的内容浏览器 “/内容/Blueprints” 目录下新建文件夹 “HUD”,然后新建一个 “控件蓝图” 类 “WBP_OverheadWidget”。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

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

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


35.3 显示本地网络角色

  1. 返回 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)*/
    
  1. 打开 “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)*/
    }...
    
  2. 在虚幻引擎打开 “BP_BlasterCharacter” 蓝图编辑器,在左侧 “组件”(Component)面板中可以看到头部组件 “OverheadWidget” 已经附加到 “胶囊体组件 (CollisionCylinder)”(Capsule Component(CollisionCylinder)) ,点击它,并在右侧 “细节”(Details)面板 “用户界面”(USER INTERFACE)选项卡下将 “空间”(Space)从 “世界”(World) 设置为 “屏幕”(Screen),将 “控件类”(Widget Class)设置为 “WBP_OverheadWidget”,勾选 “以所需大小绘制”(Draw at Desired Size),这样我们就不必手动设置(Manually set)这个部件的大小。在这里插入图片描述

  3. 在左侧 “组件”(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”)。
    在这里插入图片描述

  4. 点击上方 “视口”(Viewport) 选项卡,接着在左侧 “组件”(Component)面板中点击 “OverheadWidget”,然后在 “视口” 中将该组件拖拽移动至人物头顶。
    在这里插入图片描述

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

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


35.4 显示远程网络角色

  1. 返回 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 显示的文本			
    }...
    
  2. 点击工具栏的 “播放”()按钮启动运行,可以看到视口面板中的玩家是监听服务器,弹出的两个窗口为客户端。视口面板中能被控制的那个人物(下图红圈标注)具有 远程 “Autonomous Proxy” 角色(自主代理 / 主动端),其他不能被控制的人物具有 远程 “Simulated Proxy” 角色(模拟代理 / 模拟端);而在客户端中,所有人物无论可不可以被控制,远程 网络角色都是 “Authority”。由此我们就可以根据人物 本地远程 网络角色来判断玩家的机器属于服务器端还是客户端。
    在这里插入图片描述

    在 Actor 的复制过程中,有两个属性扮演了重要角色,分别是 Role 和 RemoteRole。
    有了这两个属性,您可以知道:

    • 谁拥有 actor 的主控权
    • actor 是否被复制
    • 复制模式

    首先一件要确定的事,就是谁拥有特定 actor 的主控权。要确定当前运行的引擎实例是否有主控者,需要查看 Role 属性是否为 ROLE_Authority。如果是,就表明这个运行中的 虚幻引擎 实例负责掌管此 actor(决定其是否被复制)。
    如果 Role 是 ROLE_Authority,RemoteRole 是 ROLE_SimulatedProxyROLE_AutonomousProxy就说明这个引擎实例负责将此 actor 复制到远程连接

    就目前而言,只有服务器能够向已连接的客户端同步 Actor (客户端永远都不能向服务器同步)。始终记住这一点, 只有 服务器 才能看到 Role == ROLE_AuthorityRemoteRole == 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” 和 “RemoteRole” 值可能发生对调,即如果服务器上有这样的配置 “(Local)Role == ROLE_Authority” 以及“RemoteRole == ROLE_SimulatedProxy”,客户端会将其识别为 “(Local)Role == ROLE_SimulatedProxy” 以及 “RemoteRole == ROLE_Authority”,这种情况是正常的,因为服务器要负责掌管 Actor 并将其复制到客户端。而客户端只是接收更新,并在更新的间歇模拟 Actor。


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

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

相关文章

K8s 1.27.1 实战系列(七)Deployment

一、Deployment介绍 Deployment负责创建和更新应用程序的实例,使Pod拥有多副本,自愈,扩缩容等能力。创建Deployment后,Kubernetes Master 将应用程序实例调度到集群中的各个节点上。如果托管实例的节点关闭或被删除,Deployment控制器会将该实例替换为群集中另一个节点上的…

Linux(Centos 7.6)命令详解:vim

1.命令作用 vi/vim 是Linux 系统内置不可或缺的文本编辑命令&#xff0c;vim 是vi 的加强版本&#xff0c;兼容vi 的所有指令&#xff0c;不仅能编辑文本&#xff0c;而且还具有shell 程序编辑的功能&#xff0c;可以不同颜色的字体来辨别语法的正确性。 2.命令语法 usage: …

微信小程序引入vant-weapp组件教程

本章教程,介绍如何在微信小程序中引入vant-weapp。 vant-weapp文档:https://vant-ui.github.io/vant-weapp/#/button 一、新建一个小程序 二、npm初始化 npm init三、安装 Vant Weapp‘ npm i @vant/weapp -

C++ 作业 DAY5

作业 代码 Widtget.h class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget();private:Ui::Widget *ui;/************************ 起始终止坐标 ************************/QPoint end;QPoint start;QVector<QPoint> per_start_lis…

Selenium 中 ActionChains 支持的鼠标和键盘操作设置及最佳实践

Selenium 中 ActionChains 支持的鼠标和键盘操作设置及最佳实践 一、引言 在使用 Selenium 进行自动化测试时&#xff0c;ActionChains 类提供了强大的功能&#xff0c;用于模拟鼠标和键盘的各种操作。通过 ActionChains&#xff0c;可以实现复杂的用户交互&#xff0c;如鼠标…

前端面试技术性场景题

87.场景面试之大数运算&#xff1a;超过js中number最大值的数怎么处理 在 JavaScript 中&#xff0c;Number.MAX_SAFE_INTEGER&#xff08;即 2^53 - 1&#xff0c;即 9007199254740991&#xff09;是能被安全表示的最大整数。超过此值时&#xff0c;普通的 Number 类型会出现…

【js逆向】iwencai国内某金融网站实战

地址&#xff1a;aHR0cHM6Ly93d3cuaXdlbmNhaS5jb20vdW5pZmllZHdhcC9ob21lL2luZGV4 在搜索框中随便输入关键词 查看请求标头&#xff0c;请求头中有一个特殊的 Hexin-V,它是加密过的&#xff1b;响应数据包中全是明文。搞清楚Hexin-V的值是怎么生成的&#xff0c;这个值和cooki…

ES Module 的 import 导入和 import () 动态导入

ES Module 的 import 导入和 import () 动态导入介绍 一、ES Module 简介 ES Module 是 JavaScript 官方提供的标准化模块系统&#xff0c;它的出现解决了长期以来 JavaScript 在模块管理方面的混乱局面。通过 ES Module&#xff0c;开发者可以更加方便地组织和复用代码&…

使用Node.js从零搭建DeepSeek本地部署(Express框架、Ollama)

目录 1.安装Node.js和npm2.初始化项目3.安装Ollama4.下载DeepSeek模型5.创建Node.js服务器6.运行服务器7.Web UI对话-Chrome插件-Page Assist 1.安装Node.js和npm 首先确保我们机器上已经安装了Node.js和npm。如果未安装&#xff0c;可以通过以下链接下载并安装适合我们操作系…

BUUCTF——[GYCTF2020]FlaskApp1 SSTI模板注入/PIN学习

目录 一、网页功能探索 二、SSTI注入 三、方法一 四、方法二 使用PIN码 &#xff08;1&#xff09;服务器运行flask登录所需的用户名 &#xff08;2&#xff09;modename &#xff08;3&#xff09;flask库下app.py的绝对路径 &#xff08;4&#xff09;当前网络的mac地…

Java基础关键_018_集合(二)

目 录 一、泛型 ※ 1.说明 2.实例 3.擦除与补偿 4.泛型的定义 &#xff08;1&#xff09;类定义 &#xff08;2&#xff09;静态方法定义 &#xff08;3&#xff09;接口定义 5.通配符 &#xff08;1&#xff09;无限定 &#xff08;2&#xff09;上限 &#xff…

FPGA学习篇——Verilog学习3(关键字+注释方法+程序基本框架)

1 Verilog常用关键字 大概知道以下哪些是关键字就好&#xff0c;如何使用还是得在编写代码中来学习。 2 Verilog注释方法 Verilog有两种注释方式&#xff1a; 2.1 “ // ” 单行。 2.2 “ /* ... */ ” 可扩展多行。 3 Verilog程序基本框架 Verilog 的基本设计单元是“…

FPGA之USB通信实战:基于FX2芯片的Slave FIFO回环测试详解

FPGA之Usb数据传输 Usb 通信 你也许会有疑问&#xff0c;明明有这么多通信方式和数据传输&#xff08;SPI、I2C、UART、以太网&#xff09;为什么偏偏使用USB呢? 原因有很多&#xff0c;如下&#xff1a; 1. 高速数据传输能力 高带宽&#xff1a;USB接口提供了较高的数据传…

深入理解与配置 Nginx TCP 日志输出

一、背景介绍 在现代网络架构中&#xff0c;Nginx 作为一款高性能的 Web 服务器和反向代理服务器&#xff0c;广泛应用于各种场景。除了对 HTTP/HTTPS 协议的出色支持&#xff0c;Nginx 从 1.9.0 版本开始引入了对 TCP 和 UDP 协议的代理功能&#xff0c;这使得它在处理数据库…

【大模型安全】安全解决方案

【大模型安全】安全解决方案 1.技术层面2.数据层面数据收集阶段训练阶段模型推理阶段 1.技术层面 在使用大语言模型时&#xff0c;通常有几种选择&#xff1a;一种是采用封装好的大语言模型SaaS云服务&#xff1b;另一种是在公有云上部署自有的大语言模型&#xff0c;并通过权…

python中httpx库的详细使用及案例

文章目录 1. 安装 httpx2. 同步请求3. 异步请求4. 高级功能5. 错误处理6. 配置客户端7. 结合 Beautiful Soup 使用8. 示例:抓取并解析网页9. 注意事项httpx 是一个现代化的 Python HTTP 客户端库,支持同步和异步请求,功能强大且易于使用。它比 requests 更高效,支持 HTTP/2…

OpenCV计算摄影学(19)非真实感渲染(Non-Photorealistic Rendering, NPR)

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 非真实感渲染&#xff08;Non-Photorealistic Rendering, NPR&#xff09;是一种计算机图形学技术&#xff0c;旨在生成具有艺术风格或其他非现实…

微信小程序点击按钮,将图片下载到本地

前言&#xff1a; 最近在公司完成一个小程序的时候需要实现一个功能&#xff1a;点击按钮获取用户相册权限&#xff0c;将图片下载到用户本地相册&#xff0c;经过了好几次的尝试最终算是实现了。将总结的经验在这里分享给小伙伴们。 实现方式&#xff1a; //.wxml文件 <…

数据仓库为什么要分层

数据仓库分层架构是数据仓库设计中的一个重要概念&#xff0c;其主要目的是为了更好地组织和管理数据&#xff0c;提高数据仓库的可维护性、可扩展性和性能。分层架构将数据仓库划分为多个层次&#xff0c;每个层次都有其特定的职责和功能。以下是数据仓库分层的主要原因和好处…

selenium库

一、什么是selenium库&#xff1f; selenim是一个用于Web应用程序自动化测试工具&#xff0c;selenium测试直接运行在浏览器中 像真正的用户在操作一样&#xff0c;驱动浏览器执行特定的动作&#xff0c;如点击&#xff0c;下拉等操作 二、selenium在爬虫中的应用 获取动态…