《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.pswp.cn/news/897229.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/897229.shtml
英文地址,請注明出處:http://en.pswp.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在爬蟲中的應用 獲取動態…