本文為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_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。