本文為B站系列教學視頻 《UE5_C++多人TPS完整教程》 —— 《P43 瞄準(Aiming)》 的學習筆記,該系列教學視頻為計算機工程師、程序員、游戲開發者、作家(Engineer, Programmer, Game Developer, Author) Stephen Ulibarri 發布在 Udemy 上的課程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻譯版,UP主(也是譯者)為 游戲引擎能吃么。
文章目錄
- P43 瞄準(Aiming)
- 43.1 創建瞄準動作事件
- 43.2 創建站立、蹲伏瞄準動畫藍圖
- 43.3 瞄準相關的變量復制及 RPC 函數
- 43.4 Summary
P43 瞄準(Aiming)
本節課我們將學習瞄準,這涉及瞄準動畫姿勢的改變。瞄準與槍戰功能組件有關,我們將在槍戰功能組件中實現瞄準,以適配多人游戲。
43.1 創建瞄準動作事件
-
在虛幻引擎打開 “項目設置”(Project Settings),在 “引擎”(Engine)下找到 “輸入”(Input),添加 “動作映射”(Action Mappings)“
Aim
”,當我們單擊 “鼠標右鍵”(Right Mouse Button) 時能使得人物角色進行瞄準。
-
我們想持續按住 “鼠標右鍵” 時進行瞄準,松開 “鼠標右鍵” 時停止瞄準。因此,在 “
BlasterCharacter.h
” 中聲明動作映射 “Aim
” 的回調函數 “AimButtonPressed()
” 和 “AimButtonReleased()
”,接著在 “BlasterCharacter.cpp
” 的 “SetupPlayerInputComponent()
” 函數中綁定回調函數 “AimButtonPressed()
” 和 “AimButtonReleased()
”。/*** BlasterCharacter.h ***/...UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()...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); // 人物角色俯視或仰視// 與動作映射相對應的回調函數void EquipButtonPressed(); // 人物角色裝備武器void CrouchButtonPressed(); // 人物角色蹲伏/* P43 瞄準(Aiming)*/void AimButtonPressed(); // 人物角色開始瞄準void AimButtonReleased(); // 人物角色停止瞄準/* P43 瞄準(Aiming)*/...};
/*** BlasterCharacter.cpp ***/...// Called to bind functionality to input void ABlasterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) {Super::SetupPlayerInputComponent(PlayerInputComponent);// 綁定動作映射PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);PlayerInputComponent->BindAction("Equip", IE_Pressed, this, &ABlasterCharacter::EquipButtonPressed);PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &ABlasterCharacter::CrouchButtonPressed);/* P43 瞄準(Aiming)*/PlayerInputComponent->BindAction("Aim", IE_Pressed, this, &ABlasterCharacter::AimButtonPressed);PlayerInputComponent->BindAction("Aim", IE_Released, this, &ABlasterCharacter::AimButtonReleased);/* P43 瞄準(Aiming)*/// 綁定軸映射PlayerInputComponent->BindAxis("MoveForward", this, &ABlasterCharacter::MoveForward);PlayerInputComponent->BindAxis("MoveRight", this, &ABlasterCharacter::MoveRight);PlayerInputComponent->BindAxis("Turn", this, &ABlasterCharacter::Turn);PlayerInputComponent->BindAxis("LookUp", this, &ABlasterCharacter::LookUp); }.../* P43 瞄準(Aiming)*/ // 按住鼠標右鍵開始瞄準 void ABlasterCharacter::AimButtonPressed() {}// 松開鼠標右鍵停止瞄準 void ABlasterCharacter::AimButtonReleased() {} /* P43 瞄準(Aiming)*/
-
我們添加一些變量或函數來記錄我們是否在瞄準。在 “
CombatComponent.h
” 中定義一個布爾變量 “bAiming
”,接著在 “BlasterCharacter.h
” 中創建一個返回值為布爾變量的函數 “IsAiming()
” 并在 “BlasterCharacter.cpp
” 中仿照函數 “IsWeaponEquipped()
” 完成 “IsAiming()
” 定義。在 “BlasterAnimInstance.h
” 中定義一個布爾變量 “bIsAiming
”,用以記錄人物角色是否在瞄準;隨后,在 “BlasterAnimInstance.cpp
” 的 “NativeUpdateAnimation()
” 函數中通過調用 “BlasterCharacter
” 的 “IsAiming()
” 函數更新 “bAiming
” 的值。/*** CombatComponent.h ***/...UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class BLASTER_API UCombatComponent : public UActorComponent {GENERATED_BODY()...private:class ABlasterCharacter* Character; // 聲明人物角色類,避免反復 casting 到 ABlasterCharacterUPROPERTY(Replicated)class AWeapon* EquippedWeapon; // 保存當前裝備的武器/* P43 瞄準(Aiming)*/bool bAiming; // 是否在瞄準/* P43 瞄準(Aiming)*/ };
/*** BlasterCharacter.h ***/UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()...public: ...bool IsWeaponEquipped(); // 判斷是否裝備了武器/* P43 瞄準(Aiming)*/bool IsAiming(); // 判斷是否在瞄準/* P43 瞄準(Aiming)*/ };
/*** BlasterCharacter.cpp ***/...bool ABlasterCharacter::IsWeaponEquipped() {return (Combat && Combat->EquippedWeapon); // 返回值為是否裝備了武器 }/* P43 瞄準(Aiming)*/ bool ABlasterCharacter::IsAiming() {return (Combat && Combat->bAiming); // 返回值為是否裝備了武器 } /* P43 瞄準(Aiming)*/
/*** BlasterAnimInstance.h ***/...UCLASS() class BLASTER_API UBlasterAnimInstance : public UAnimInstance {GENERATED_BODY()...private:...UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 屬性說明符:僅在藍圖中可讀,類別為 “Movemonet”;bool bWeaponEquipped; // 是否裝備了武器UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 屬性說明符:僅在藍圖中可讀,類別為 “Movemonet”;bool bIsCrouched; // 是否在蹲伏/* P43 瞄準(Aiming)*/UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 屬性說明符:僅在藍圖中可讀,類別為 “Movemonet”;bool bAiming;/* P43 瞄準(Aiming)*/ };
/*** BlasterAnimInstance.cpp ***/...void UBlasterAnimInstance::NativeUpdateAnimation(float DeltaTime) // 原生(Native)類更新函數 NativeUpdateAnimation() 覆寫,用于在每一幀調用以更新動畫 {...bWeaponEquipped = BlasterCharacter->IsWeaponEquipped(); // 調用 BlasterCharacter 的 IsWeaponEquipped() 函數判斷人物角色是否裝備了武器bIsCrouched = BlasterCharacter->bIsCrouched; // 訪問 BlasterCharacter 的 bIsCrouched 變量值來判斷人物角色是否在蹲伏/* P43 瞄準(Aiming)*/bAiming = BlasterCharacter->IsAiming(); // 調用 BlasterCharacter 的 IsAiming() 函數來判斷人物角色是否在瞄準/* P43 瞄準(Aiming)*/ }...
-
回到 “
BlasterCharacter.cpp
” 中對動作映射 “Aim
” 的回調函數 “AimButtonPressed()
” 和 “AimButtonReleased()
” 進行簡單的定義,隨后進行編譯。/*** BlasterCharacter.cpp ***/.../* P43 瞄準(Aiming)*/ // 按住鼠標右鍵進行瞄準 void ABlasterCharacter::AimButtonPressed() {if (Combat) {Combat->bAiming = true;} }// 松開鼠標右鍵取消瞄準 void ABlasterCharacter::AimButtonReleased() {if (Combat) {Combat->bAiming = false;} } /* P43 瞄準(Aiming)*/...
43.2 創建站立、蹲伏瞄準動畫藍圖
-
在虛幻引擎中打開動畫藍圖 “
BlasterAnimBP
”,然后在 “AnimGraph
” 面板中打開狀態機 “Equipped
” 的 “Idle
” 狀態編輯界面,在右側內容瀏覽器中將動畫資產 “Idle_Ironsights
” 拖拽至面板中生成 “序列播放器 Idle_Rifle_Ironsights”(Play Idle_Rifle_Ironsights) 藍圖節點;接著,新增節點 “按布爾混合姿勢”(Blend Poses by bool)以及 “獲取 Aiming”(Get Aiming),并將生成的藍圖變量 “Aiming
” 連接到 “按布爾混合姿勢” 節點的 “Active Value
” 引腳;然后,將 “序列播放器 Idle_Rifle_Ironsights” 節點的輸出引腳連接到 “按布爾混合姿勢” 節點的 “真 姿勢”(True Pose)引腳,將原先的 “序列播放器 Idle_Rifle_Hip” 節點的輸出引腳連接到 “按布爾混合姿勢” 節點的 “False 姿勢”(False Pose)引腳,最后將 “按布爾混合姿勢” 的輸出引腳連接到 “輸出動畫姿勢”(Output Animation Pose) 節點的 “Result
” 輸入引腳上。這段藍圖表示如果 “Aiming
” 的值為 “true
” 則輸出人物角色站立瞄準姿勢,否則輸出站立待機姿勢。
-
在 “
AnimGraph
” 面板中打開狀態機 “Equipped
” 的 “CrouchIdle
” 狀態編輯界面,在右側內容瀏覽器中將動畫資產 “Crouch_Idle_Rifle_Ironsights
” 拖拽至面板中生成 “序列播放器 Crouch_Idle_Rifle_Ironsights”(Play Crouch_Idle_Rifle_Ironsights) 藍圖節點,然后仿照 步驟 1 繪制如下圖所示的藍圖。這段藍圖表示如果 “Aiming
” 的值為 “true
” 則輸出人物角色蹲伏瞄準姿勢,否則輸出蹲伏待機姿勢。
-
編譯、保存后進行測試。我們操控一個客戶端上的人物角色進行瞄準時,在本地可以看到站立和蹲伏的瞄準動畫姿勢,但是在另一個客戶端和服務器上均看不到本地的人物角色在瞄準,這說明我們需要對瞄準動作事件中的變量進行復制,創建相應的 RPC 函數,以實現瞄準動畫姿勢的網絡同步。
43.3 瞄準相關的變量復制及 RPC 函數
-
在 “
CombatComponent.h
” 中將 “bAiming
” 重新定義為可復制的變量,在 “CombatComponent.cpp
” 的 “GetLifetimeReplicatedProps
” 函數中添加 “DOREPLIFETIME()
” 宏調用,以在派生的 “UCombatComponent
” 實例的生命周期內復制 “bAming
”。/*** CombatComponent.h ***/...UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class BLASTER_API UCombatComponent : public UActorComponent {GENERATED_BODY()...private:class ABlasterCharacter* Character; // 聲明人物角色類,避免反復 casting 到 ABlasterCharacterUPROPERTY(Replicated)class AWeapon* EquippedWeapon; // 保存當前裝備的武器/* P43 瞄準(Aiming)*/UPROPERTY(Replicated) // 可復制變量bool bAiming; // 是否在瞄準/* P43 瞄準(Aiming)*/ };
/*** CombatComponent.cpp ***/...// 重寫復制屬性函數 void UCombatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {// 調用SuperSuper::GetLifetimeReplicatedProps(OutLifetimeProps);// 添加要為派生的類 UCombatComponent 復制的屬性,需要添加頭文件 "Net/UnrealNetwork.h"// DOREPLIFETIME 宏用于指定 UCombatComponent 哪些屬性需要被復制,以及復制的條件。DOREPLIFETIME(UCombatComponent, EquippedWeapon);/* P43 瞄準(Aiming)*/DOREPLIFETIME(UCombatComponent, bAiming);/* P43 瞄準(Aiming)*/ }
-
編譯后進行測試,當我們操控服務器上的人物角色進行站立和蹲伏的瞄準時,所有的客戶端都可以看到服務器上的人物角色在進行瞄準;但當我們在客戶端上進行瞄準時,只有本地的客戶端自己可見,另一個客戶端和服務器上均不可見。這再次說明變量復制是單向的,只能從服務器到客戶端,因此需要實現從客戶端到服務器 RPC 函數。
-
在 “
CombatComponent.h
” 中聲明一個可以設置 “bAiming
” 的值的函數 “SetAiming()
”,接著使用帶有 “Server
” 和 “Reliable
” 關鍵字的 “UFUNCTION()
” 宏聲明服務器可靠 RPC 函數 “ServerSetAiming()
”。接著,在 “CombatComponent.cpp
” 中完成 “SetAiming()
” 和 “ServerSetAiming()
” 的定義,并在 “ServerSetAiming()
” 函數名后添加 “_Implementation
” 使之成為 “實施函數”。隨后,修改 “BlasterCharacter.cpp
” 中動作映射 “Aim
” 的回調函數 “AimButtonPressed()
” 和 “AimButtonReleased()
” 的定義,以調用 “SetAiming()
” 函數。/*** CombatComponent.h ***/...UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class BLASTER_API UCombatComponent : public UActorComponent {GENERATED_BODY()...protected:// Called when the game startsvirtual void BeginPlay() override;/* P43 瞄準(Aiming)*/void SetAiming(bool bIsAiming);UFUNCTION(Server, Reliable)void ServerSetAiming(bool bIsAiming);/* P43 瞄準(Aiming)*/private:class ABlasterCharacter* Character; // 聲明人物角色類,避免反復 casting 到 ABlasterCharacterUPROPERTY(Replicated)class AWeapon* EquippedWeapon; // 保存當前裝備的武器/* P43 瞄準(Aiming)*/UPROPERTY(Replicated) // 可復制變量bool bAiming; // 是否在瞄準/* P43 瞄準(Aiming)*/ };
/*** CombatComponent.cpp ***/.../* P43 瞄準(Aiming)*/ // 設置 bAiming void UCombatComponent::SetAiming(bool bIsAiming) {bAiming = bIsAiming;// 由虛幻引擎官方文檔 “從服務器調用的 RPC” 的表格 “Server” 這一列和 “從客戶端調用的 RPC” 的表格第一行 “Server” 列可知,// 以 Server 關鍵字聲明的 RPC 函數無論在客戶端還是服務器上調用都是在服務器上執行,因此無需對當前機器是客戶端還是服務器端進行判斷。//if (!Character->HasAuthority()) {// ServerSetAiming(bIsAiming);//}ServerSetAiming(bIsAiming); }// 服務器可靠 RPC 函數,bAiming 會被服務器復制到所有客戶端 void UCombatComponent::ServerSetAiming_Implementation(bool bIsAiming) {bAiming = bIsAiming; // 由于函數在服務器上執行且 bAiming 可復制,服務器會將 bAiming 值的更新復制到所有客戶端 } /* P43 瞄準(Aiming)*/
/*** BlasterCharacter.cpp ***/ /* P43 瞄準(Aiming)*/ // 按住鼠標右鍵進行瞄準 void ABlasterCharacter::AimButtonPressed() {if (Combat) {/*Combat->bAiming = true;*/Combat->SetAiming(true);} }// 松開鼠標右鍵取消瞄準 void ABlasterCharacter::AimButtonReleased() {if (Combat) {/*Combat->bAiming = false;*/Combat->SetAiming(false);} } /* P43 瞄準(Aiming)*/
要求和注意事項
您必須滿足一些要求才能充分發揮 RPC 的作用:- 它們必須從 Actor 上調用。
- Actor 必須被復制。
- 如果 RPC 是從服務器調用并在客戶端上執行,則只有實際擁有這個 Actor 的客戶端才會執行函數。
- 如果 RPC 是從客戶端調用并在服務器上執行,客戶端就必須擁有調用 RPC 的 Actor。
多播 RPC 則是個例外:
- 如果它們是從服務器調用,服務器將在本地和所有已連接的客戶端上執行它們。
- 如果它們是從客戶端調用,則只在本地而非服務器上執行。
現在,我們有了一個簡單的多播事件限制機制:在特定 Actor 的網絡更新期內,多播函數將不會復制兩次以上。按長期計劃,我們會對此進行改善,同時更好的支持跨通道流量管理與限制。
下面的表格根據執行調用的 actor 的所有權(最左邊的一列),總結了特定類型的 RPC 將在哪里執行。
從服務器調用的 RPC
Actor 所有權 未復制 NetMulticast Server Client Client-owned actor 在服務器上運行 在服務器和所有客戶端上運行 在服務器上運行 在 actor 的所屬客戶端上運行 Server-owned actor 在服務器上運行 在服務器和所有客戶端上運行 在服務器上運行 在服務器上運行 Unowned actor 在服務器上運行 在服務器和所有客戶端上運行 在服務器上運行 在服務器上運行 從客戶端調用的 RPC
Actor 所有權 未復制 NetMulticast Server Client Owned by invoking client 在執行調用的客戶端上運行 在執行調用的客戶端上運行 在服務器上運行 在執行調用的客戶端上運行 Owned by a different client 在執行調用的客戶端上運行 在執行調用的客戶端上運行 丟棄 在執行調用的客戶端上運行 Server-owned actor 在執行調用的客戶端上運行 在執行調用的客戶端上運行 丟棄 在執行調用的客戶端上運行 Unowned actor 在執行調用的客戶端上運行 在執行調用的客戶端上運行 丟棄 在執行調用的客戶端上運行
—— 虛幻引擎官方文檔《虛幻引擎中的 RPC》
-
編譯后進行測試,當我們操控一個客戶端上的人物角色進行瞄準時,在本地客戶端可以看到站立和蹲伏的瞄準動畫姿勢,在另一個客戶端和服務器上也能都能看到。
43.4 Summary
本節課我們實現了人物角色的瞄準功能及其網絡同步機制。首先,在項目設置中添加 “Aim
” 動作映射,綁定為鼠標右鍵,然后在人物角色類 “ABlasterCharacter
” 中聲明并實現了該動作映射下按住與釋放鼠標右鍵的回調函數。接著,在槍戰組件 “UCombatComponent
”中創建了布爾變量 “bAiming
” 表示人物角色的瞄準狀態,并通過人物角色類 “ABlasterCharacter
” 的 “IsAiming()
” 方法供動畫實例 “UBlasterAnimInstance
” 進行訪問,同時 “UBlasterAnimInstance
” 中新增 “bIsAiming
” 變量,在每幀更新時同步人物角色瞄準狀態。然后,在動畫藍圖中重構了持槍狀態機的動畫邏輯,即人物角色在站立與蹲伏狀態下,根據“bIsAiming
” 變量切換待機姿勢與瞄準姿勢。
在進行測試時,我們發現本地操控人物角色進行瞄準時可以看到對應的動畫姿勢,但其他機器上卻無法看到,因而需要對瞄準相關的變量進行網絡同步。首先,將槍戰組件的 “bAiming
” 注冊為復制屬性,這樣服務器上人物角色的瞄準狀態能復制、更新到所有客戶端,當服務器上的人物角色進行瞄準時,所有客戶端也能看到;隨后,創建服務器可靠 的 RPC 函數 “ServerSetAiming()
”,當客戶端操控人物角色進行瞄準時通過“SetAiming()
” 方法調用該 RPC 函數,并在服務器執行,保證將客戶端上人物角色的瞄準狀態更新到其他客戶端。最終,測試結果驗證了所有客戶端都能正確顯示本地及其他玩家的瞄準狀態,實現多人游戲下瞄準狀態及動畫姿勢的網絡同步。