《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》

news2025/6/7 7:17:00

本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P38 变量复制(Variable Replication)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。

在这里插入图片描述


文章目录

  • P38 变量复制(Variable Replication)
  • 38.1 创建武器重叠属性
  • 38.2 创建 Repnotify 函数
  • 38.3 创建拾取组件结束重叠事件
  • 38.4 Summary


P38 变量复制(Variable Replication)

本节课的目标是显示拾取组件在客户端上,这涉及到复制变量(Replicating variables);我们将学习创建一个函数,当变量被复制时它会被客户端调用,这样的的函数被被称为 “RepNotify” 函数(更新通知 “复制” 函数)。
在这里插入图片描述


38.1 创建武器重叠属性

  1. 我们需要创建一个可以存储与人物角色重叠的武器的变量,因此需要在 “BlasterCharacter.h” 中使用添加了元数据说明符 “Replicated“ 的 ”UPROPERTY“ 宏声明武器类变量实例 “OverlappingWeapon” 作为 Actor 类 “BlasterCharacter” 的属性,当这个属性在服务器上发生变化时,服务器会在复制的属性每次更改其值时向每个连接的客户端发送更新,每个客户端会将更新的值应用到其本地版本的 ”BlasterCharacter”。此外,我们还需声明重写 “GetLifetimeReplicatedProps()“ 函数并添加宏调用,以在派生的 ”BlasterCharacter” 实例的生命周期内复制属性;使用 forceinline 宏声明“SetOverlappingWeapon()“ 函数,用以设置重叠的武器实例,之后将在武器类 Weapon 的 OnSphereOverlap() 函数中调用。

    /*** BlasterCharacter.h ***/
    ...
    
    UCLASS()
    class BLASTER_API ABlasterCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    public:
    	// Sets default values for this character's properties
    	ABlasterCharacter();
    
    	// Called every frame
    	virtual void Tick(float DeltaTime) override;
    
    	// Called to bind functionality to input
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    
    	/* P38 变量复制(Variable Replication)*/
    	// 重写复制属性函数
    	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
    	/* P38 变量复制(Variable Replication)*/
    	
    	...
    
    private:
    	// VisibleAnywhere:表示该变量在蓝图编辑器中显示在变量列表中,并且可以在蓝图中进行读取操作。常用于定义可读变量。
    	UPROPERTY(VisibleAnywhere, Category = Camera)	
    	class USpringArmComponent* CameraBoom;			// 添加弹簧臂组件,归类为 “Camera”
    
    	UPROPERTY(VisibleAnywhere, Category = Camera)
    	class UCameraComponent* FollowCamera;			// 添加摄像机组件,归类为 “Camera”
    
    	// BlueprintReadOnly:表示该变量只能在蓝图中进行读取操作,不能在蓝图中进行写入操作。常用于定义只读变量。
    	// 我们不能在私有变量中使用关键字 BlueprintReadOnly和 BlueprintReadWrite,除非使用了 meta = (AllowPrivateAccess = "true") 进行指定
    	// UE4中用于定义蓝图变量的元数据(metadata)的所有关键字及其解释和作用可以参见:https://blog.csdn.net/u013007305/article/details/130450354
    	UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))	
    	class UWidgetComponent* OverheadWidget;												// 添加头部组件
    
    	/* P38 变量复制(Variable Replication)*/
    	UPROPERTY(Replicated)
    	class AWeapon* OverlappingWeapon;
    	
    public:	
    	// 设置重叠的武器实例,以便在武器类 Weapon 的 OnSphereOverlap() 函数中调用 
    	FORCEINLINE void SetOverlappingWeapon(AWeapon* Weapon) { OverlappingWeapon = Weapon; }
    };
    	/* P38 变量复制(Variable Replication)*/
    

    forceinline 是编程中用于强制内联函数的关键字或注解‌,主要用于减少函数调用开销,但需谨慎使用以避免代码膨胀或性能下降。

    核心作用
    - 强制内联展开‌:与普通inline不同,forceinline会跳过编译器的成本/收益分析,直接依据程序员指定展开函数体到调用处。‌‌
    ‌ - 性能优化‌:消除函数调用时的栈帧开销,适用于频繁调用的小型函数。‌‌
    ‌- 潜在风险‌:过度使用可能导致代码体积膨胀、缓存命中率下降,甚至引发性能倒退。
    使用建议‌:
    ‌ - 适用场景‌:高频调用的简单函数(如getter/setter)、性能关键路径代码。‌‌
    ‌- 禁用情况‌:递归函数、虚函数调用或含复杂控制流的函数。‌‌
    - 调试兼容性‌:需关闭内联(如C++的/Ob0选项)时,forceinline可能失效。


    —— 百度 AI

    在 “BlasterCharacter.cpp” 中完成 “GetLifetimeReplicatedProps()” 的定义 “OverlappingWeapon”。这样,属性 “OverlappingWeapon” 就只会在服务器上发生改变时进行复制,而不会在每一帧(Frame)或每一次网络更新(Net update)中被复制。

    /*** BlasterCharacter.cpp ***/
    
    /* P38 变量复制(Variable Replication)*/
    #include "BlasterCharacter.h"
    #include "GameFramework/SpringArmComponent.h"
    #include "Camera/CameraComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "Components/WidgetComponent.h"
    
    /* P38 变量复制(Variable Replication)*/
    #include "Net/UnrealNetwork.h"
    /* P38 变量复制(Variable Replication)*/
    
    ...
    
    void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	// 调用Super
    	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    	// 添加要为派生的类 ABlasterCharacter 复制的属性,需要添加头文件 "Net/UnrealNetwork.h" 
    	DOREPLIFETIME(ABlasterCharacter, OverlappingWeapon);
    }
    /* P38 变量复制(Variable Replication)*/
    

    属性复制
    每个 Actor 都维护一个全属性列表,其中包含 “Replicated” 说明符。每当复制的属性值发生变化时,服务器会向所有客户端发送更新。客户端会将其应用到 Actor 的本地版本上。这些更新只会来自服务器,客户端永远不会向服务器或其他客户端发送属性更新。

    我们不推荐在客户端上更改复制的变量值。该值将始终与服务器端的值不一致,直到服务器下一次侦测到变更并发送更新为止。如果服务器版本的属性不是经常更新,那客户端就需要等待很长时间才能被纠正。
    Actor 属性复制可靠。这意味着,Actor的客户端版本的属性最终将反映服务器上的值,但客户端不必接受服务器上某个属性的每一个单独变更。例如,如果一个整数属性的值快速从 100 变成 200,然后又变成了 300,客户端将最终接受一个值为 300 的变更,但客户端不一定会知道这个值曾经变成过 200。


    —— 虚幻引擎官方文档《属性复制》

    复制Actor属性
    一般来说,复制Actor属性的途径主要有两个:

    • Replicated
    • ReplicatedUsing

    这些是虚幻引擎的反射系统使用的两个属性元数据说明符。“Replicated” 属性为属性复制提供了指定特定条件的选项,将属性复制限制在特定连接上。你也可以设置自定义复制条件,为属性复制定义自己的逻辑。“ReplicatedUsing” 属性需要你提供 “RepNotify” 函数,当相关属性被复制时,客户端就会调用该函数。
    你还可以使用 “NotReplicated” 说明符指定 不 复制的属性。此说明符可能一开始看起来没什么用,但在将要复制的结构体中某个属性设置为不复制时非常有用。


    —— 虚幻引擎官方文档《复制Actor属性》

  2. 在 “Weapon.cpp” 的 “OnSphereOverlap()” 函数中调用 “SetOverlappingWeapon(),接着在 “Weapon.h” 中声明 “ShowPickupWidget()“ 函数,并在 “Weapon.cpp” 中完成 “ShowPickupWidget()“ 函数的定义,该函数用于设置拾取组件可见;然后在尝试在 “BlasterCharacter.cpp” 的 ”Tick()“ 函数中调用 “ShowPickupWidget()“ 函数。

    /*** Weapon.cpp ***/
    
    ...
    
    void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OhterComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    {
    	// 需要先添加头文件 "Blaster/Character/BlasterCharacter.h"
    	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);	// 类型转换为 OtherActor
    	if (BlasterCharacter && PickupWidget) {										// 确保 BlasterCharacter 和 PickupWidget 都不是空指针
    		/* P38 变量复制(Variable Replication)*/
    		// PickupWidget->SetVisibility(true);									// 设置拾取组件可见
    		BlasterCharacter->SetOverlappingWeapon(this);							// 设置重叠武器类
    		/* P38 变量复制(Variable Replication)*/
    	}
    }
    
    /* P38 变量复制(Variable Replication)*/
    void AWeapon::ShowPickupWidget(bool bShowWidget)
    {
    	if (PickupWidget) {
    		PickupWidget->SetVisibility(bShowWidget);								// 设置拾取组件可见
    	}
    }
    /* P38 变量复制(Variable Replication)*/
    
    /*** BlasterCharacter.cpp ***/
    
    /* P38 变量复制(Variable Replication)*/
    #include "Net/UnrealNetwork.h"
    #include "Blaster/Weapon/Weapon.h"
    /* P38 变量复制(Variable Replication)*/
    
    ...
    
    // Called every frame
    void ABlasterCharacter::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    	/* P38 变量复制(Variable Replication)*/
    	// 如果有与人物角色重叠的武器,则在每个 tick 都显示拾取组件,需要添加头文件 "Blaster/Weapon/Weapon.h"
    	if (OverlappingWeapon) OverlappingWeapon->ShowPickupWidget(true);
    	/* P38 变量复制(Variable Replication)*/
    }
    
    ...
    
  3. 编译后在虚幻引擎中打开关卡 ”BlasterMap“ 进行测试,当我们控制其中一个客户端与武器发生重叠,可以看到拾取组件会显示在服务器和所有客户端上,这是因为在 ”Tick()“ 函数中,我们已标记可复制的属性 ”OverlappingWeapon“ 会被复制到所有客户端,但是我们只想要在这个客户端上显示拾取组件,而不想在服务器和其他客户端上上显示,说明当前方法行不通。
    在这里插入图片描述

  4. 我们尝试在 “BlasterCharacter.cpp“ 中将 “GetLifetimeReplicatedProps()“ 函数里的 ”DOREPLIFETIME()” 改为 “DOREPLIFETIME_Condition()“,设置仅拥有者可复制属性 “OverlappingWeapon”。

    /*** BlasterCharacter.cpp ***/
    
    ...
    
    /* P38 变量复制(Variable Replication)*/
    void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	// 调用Super
    	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    	// 添加要为派生的类 ABlasterCharacter 复制的属性,需要添加头文件 "Net/UnrealNetwork.h"
    	// DOREPLIFETIME(ABlasterCharacter, OverlappingWeapon);
    	DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);	// 仅拥有者可复制
    }
    /* P38 变量复制(Variable Replication)*/
    
    ...
    

复制条件引用
COND_None:没有条件,一旦更改即复制。
COND_InitialOnly:仅尝试在初始系列上复制。
COND_OwnerOnly:仅复制到 Actor 的所有者。
COND_SkipOwner:复制到除 Actor 的所有者之外的每个连接。
COND_SimulatedOnly:复制到模拟的 Actor。
COND_AutonomousOnly:仅复制到自主 Actor。
COND_SimulatedOrPhysics:复制到模拟的或 bRepPhysics Actor。
COND_InitialOrOwner:在初始系列上复制,或复制到Actor的所有者。
COND_Custom:没有特定条件,但能够打开或关闭。请参阅自定义属性复制了解更多信息。
COND_ReplayOrOwner:仅复制到重播连接或Actor的所有者。
COND_ReplayOnly:仅复制到重播连接。
COND_SimulatedOnlyNoReplay:仅复制到模拟的Actor,但不复制到重播连接。
COND_SimulatedOrPhysicsNoReplay:复制到模拟的或 bRepPhysics Actor,但不复制到重播连接。
COND_SkipReplay:不复制到重播连接。
COND_Dynamic:在运行时重载条件。默认为总是复制,除非你将其重载为新条件。
COND_Never:从不复制。


自定义属性复制
要更精细地控制 Actor 属性何时复制,你可以将 “COND_Custom” 复制条件与 “DOREPLIFETIME_ACTIVE_OVERRIDE” 宏一起使用。此宏可帮助你创建自定义条件来规定属性何时复制。此复制按 Actor 规定,而不是按连接规定。因此,在自定义条件中使用可能因连接而异的状态是不安全的。


—— 虚幻引擎官方文档《复制Actor属性》

  1. 编译后再次进行测试,可以看到当我们控制其中一个客户端人物角色移动至与武器发生重叠时,可以看到拾取组件在另一个客户端上不会显示,但还是会在服务器上显示,不符合我们的要求,这说明在 “Tick()” 函数中设置拾取组件可见的方法是行不通的。
    在这里插入图片描述

38.2 创建 Repnotify 函数

  1. 在控制一个客户端上的人物角色移动至与武器发生重叠时,我们只想要在这个客户端上显示拾取组件,而不想在服务器上和其他客户端上显示,这里就需要使用到 “Repnotify“ 函数,它可以在指定的属性发生复制时被调用以执行特定操作。

    添加 “Replicated Using” 属性
    你可以使用 “UPROPERTY“ 宏中的 ”Replicated“ 说明符复制 Actor 属性。你可以使用 “ReplicatedUsing“ 说明符在每次复制变量时执行特定操作。要在每次复制你的属性时执行操作,你可以使用 “ReplicatedUsing“ 说明符和相关联的 “RepNotify“。“RepNotify“ 是复制带 “ReplicatedUsing“ 说明符的属性时对客户端调用的 “OnRep_” 函数。


    —— 虚幻引擎官方文档《复制 Actor 属性》

  2. 在 “BlasterCharacter.h“ 中使用 “UPROPERTY“ 宏中的 ”ReplicatedUsing“ 说明符声明属性 “OverlappingWeapon”,接着声明与之相关联的 “Repnotify“ 函数 “OnRep_OverlappingWeapon()”;然后,在 “BlasterCharacter.cpp“ 中完成“OnRep_OverlappingWeapon()” 函数的定义,删除或注释 “Tick()“ 函数中设置显示拾取组件的代码。

    /*** BlasterCharacter.h ***/
    
    ...
    
    UCLASS()
    class BLASTER_API ABlasterCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	...
    
    private:
    
    	...
    
    	/* P38 变量复制(Variable Replication)*/
    	// UPROPERTY(Replicated)
    	// class AWeapon* OverlappingWeapon;
    	UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)	// 指定 OverlappingWeapon 的 Repnotify 函数为 OnRep_OverlappingWeapon()
    	class AWeapon* OverlappingWeapon;
    
    	UFUNCTION()
    	void OnRep_OverlappingWeapon();							// OverlappingWeapon 的 Repnotify 函数
    	/* P38 变量复制(Variable Replication)*/
    
    	...
    }
    
    /*** BlasterCharacter.cpp ***/
    
    ...
    /* P38 变量复制(Variable Replication)*/
    // 武器重叠属性 Repnotify 函数
    void ABlasterCharacter::OnRep_OverlappingWeapon()
    {
    	
    	// 如果有与人物角色重叠的武器,则显示拾取组件
    	if (OverlappingWeapon) {
    		OverlappingWeapon->ShowPickupWidget(true);
    	}
    	
    }
    /* P38 变量复制(Variable Replication)*/
    
    // Called every frame
    void ABlasterCharacter::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    	// /* P38 变量复制(Variable Replication)*/
    	// // 如果有与人物角色重叠的武器,则在每个 tick 都显示拾取组件
    	// if (OverlappingWeapon) OverlappingWeapon->ShowPickupWidget(true);
    	// /* P38 变量复制(Variable Replication)*/
    }
    
    ...
    
  3. 编译后进行测试,可以发现拾取组件只显示在我们控制的客户端上,在服务器上和其他客户端上都不显示。但是这里又出现一个值得注意的细节(A import detail to take note of):当我们控制服务器上的人物角色移动至与武器发生重叠时,服务器上无法显示拾取组件,显然我们在服务器上没有调用到 “Repnotify“ 函数,这是因为属性复制是单向的,只能以从服务器复制到客户端,而不能从客户端复制到服务器上,因此服务器不会调用 “Repnotify“ 函数。
    在这里插入图片描述
    在这里插入图片描述

  4. 为解决这个问题,我们不能只在 “Repnotify“ 函数中设置拾取组件可见,还需要添加单独处理服务器上设置重叠武器和显示拾取组件的代码,于是在 “BlasterCharacter.h” 和 “BlasterCharacter.cpp” 中修改 ”SetOverlappingWeapon()” 的定义,这里我们可以借助 “IsLocallyControlled()” 函数判断当前 Pawn 是否为在本地控制的人物(而不是镜像人物)。

    /*** BlasterCharacter.h ***/
    
    ...
    
    UCLASS()
    class BLASTER_API ABlasterCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	...
    
    private:
    
    	...
    
    	/* P38 变量复制(Variable Replication)*/
    	// UPROPERTY(Replicated)
    	// class AWeapon* OverlappingWeapon;
    	UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)	// 指定 OverlappingWeapon 的 Repnotify 函数为 OnRep_OverlappingWeapon()
    	class AWeapon* OverlappingWeapon;
    
    	UFUNCTION()
    	void OnRep_OverlappingWeapon();							// OverlappingWeapon 的 Repnotify 函数
    
    public:
    	// 设置重叠的武器实例,以便在武器类 Weapon 的 OnSphereOverlap() 函数中调用 
    	// FORCEINLINE void SetOverlappingWeapon(AWeapon* Weapon) { OverlappingWeapon = Weapon; }
    	void SetOverlappingWeapon(AWeapon* Weapon);
    
    	/* P38 变量复制(Variable Replication)*/
    }
    
    /*** BlasterCharacter.cpp ***/
    
    ...
    /* P38 变量复制(Variable Replication)*/
    // 武器重叠属性 Repnotify 函数
    void ABlasterCharacter::OnRep_OverlappingWeapon()
    {
    	
    	// 如果有与人物角色重叠的武器,则显示拾取组件
    	if (OverlappingWeapon) {
    		OverlappingWeapon->ShowPickupWidget(true);
    	}
    	
    }
    
    // 设置与人物角色重叠的武器实例类到属性 OverlappingWeapon 中,并设置拾取组件可见
    void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
    {
    	OverlappingWeapon = Weapon;
    
    	// 使用 IsLocallyControlled() 可以快速判断当前 Pawn 自己是否为主控端,即本地控制的人物(而不是镜像人物)
    	if (IsLocallyControlled) {
    		if (OverlappingWeapon) {
    			OverlappingWeapon->ShowPickupWidget(true);
    		}
    	}
    }
    /* P38 变量复制(Variable Replication)*/
    ...
    
  5. 编译后再次进行测试,可以发现在控制服务器上的人物角色移动至与武器发生重叠时,拾取组件也能显示在服务器上了,而在所有客户端上均不显示。
    在这里插入图片描述


38.3 创建拾取组件结束重叠事件

  1. 在测试中当我们控制人物角色移动至与武器结束重叠时,拾取组件还依然显示,接下来要在 “Weapon.h” 和 “Weapon.cpp” 中重写拾取组件结束重叠事件 “OnSphereEndOverlap()“,使得拾取组件不显示,“OnSphereEndOverlap()“ 需要动态绑定到武器球体组件 “AreaSphere” 的 “`OnComponentEndOverlap``” 的委托上。

    /*** Weapon.h ***/
    
    ...
    
    UCLASS()
    class BLASTER_API AWeapon : public AActor
    {
    	GENERATED_BODY()
    	
    	...
    
    public:	
    	// Called every frame
    	virtual void Tick(float DeltaTime) override;
    
    	UFUNCTION()					// 需要动态绑定到 OnComponentBeginOverlap 的委托上,因此需要将函数设置为 UFunction
    	virtual void OnSphereOverlap(
    		UPrimitiveComponent*  OverlappedComponent,
    		AActor* OtherActor,
    		UPrimitiveComponent* OhterComponent,
    		int32 OtherBodyIndex,
    		bool bFromSweep,
    		const FHitResult& SweepResult
    	);
    
    	/* P38 变量复制(Variable Replication)*/
    	UFUNCTION()					
    	virtual void OnSphereEndOverlap(
    			UPrimitiveComponent* OverlappedComponent,
    			AActor* OtherActor,
    			UPrimitiveComponent* OhterComponent,
    			int32 OtherBodyIndex
    	);
    	/* P38 变量复制(Variable Replication)*/
    
    	...
    }
    
    /*** Weapon.cpp ***/
    
    ...
    
    // Called when the game starts or when spawned
    void AWeapon::BeginPlay()
    {
    	Super::BeginPlay();
    
    	// 服务器将负责掌管(in charge of)所有武器权限
    	if (HasAuthority()) {															// 判断是否拥有权威角色,等同于 if (GetLocalRole() == ENetRole::ROLE_Authority) 
    		AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);		// 既进行碰撞检测,也进行碰撞响应。物体之间会检测是否发生碰撞,同时会产生物理效果
    		AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
    		AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);
    		/** Delegate for notification of start of overlap with a specific component */
    		/* DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams(FComponentBeginOverlapSignature, 
    																UPrimitiveComponent, 
    																OnComponentBeginOverlap, 
    																UPrimitiveComponent*, 
    																OverlappedComponent, 
    																AActor*, OtherActor, 
    																UPrimitiveComponent*, 
    																OtherComp, int32, 
    																OtherBodyIndex, 
    																bool,
    																bFromSweep, 
    																FHitResult&, 
    																SweepResult);
    		*/
    
    
    		/* P38 变量复制(Variable Replication)*/
    		AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap);	// 动态绑定到 OnComponentEndOverlap 的委托上
    		/* P38 变量复制(Variable Replication)*/
    	}
    
    	
    	if (PickupWidget) {									// 确保 PickupWidget 不是空指针
    		PickupWidget->SetVisibility(false);				// 先设置拾取组件不可见
    	}
    }
    
    ...
    
    void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OhterComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    {
    	
    	// 需要先添加头文件 "Blaster/Character/BlasterCharacter.h"
    	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);	// 类型转换为 OtherActor
    	if (BlasterCharacter && PickupWidget) {										// 确保 BlasterCharacter 和 PickupWidget 都不是空指针
    		/* P38 变量复制(Variable Replication)*/
    		// PickupWidget->SetVisibility(true);									// 设置拾取组件可见
    		BlasterCharacter->SetOverlappingWeapon(this);							// 设置重叠武器
    		/* P38 变量复制(Variable Replication)*/
    	}
    }
    
    /* P38 变量复制(Variable Replication)*/
    void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OhterComponent, int32 OtherBodyIndex)
    {
    	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);	// 类型转换为 OtherActor
    	if (BlasterCharacter && PickupWidget) {										// 确保 BlasterCharacter 和 PickupWidget 都不是空指针
    		BlasterCharacter->SetOverlappingWeapon(nullptr);						// 设置重叠武器为空指针
    	}
    }
    
    void AWeapon::ShowPickupWidget(bool bShowWidget)
    {
    	if (PickupWidget) {
    		PickupWidget->SetVisibility(bShowWidget);								// 设置拾取组件可见
    	}
    }
    /* P38 变量复制(Variable Replication)*/
    
  2. 在 “OnSphereEndOverlap()“ 中我们通过设置重叠武器为空指针 “nullptr” 来隐藏拾取组件,为了使得人物角色移动至与武器再次发生重叠时能够显示拾取组件,可以为 “Repnotify“ 函数 “OnRep_OverlappingWeapon()” 添加一个输入参数(只能添加一个),用来表示上一个与人物角色发生重叠的武器类实例,这样就可以在复制发生之前保存在 “BlasterCharacter” 的 “OverlappingWeapon” 属性中,这里的思路非常巧妙,详情可以参阅官方文档的说明以及代码中的注释。

    RepNotify” 中参数的用法
    虚幻引擎的复制系统支持在属性的 “RepNotify” 中传递类型与复制的属性相同的参数。如果你在 “RepNotify“ 中传递参数,复制系统会自动将复制属性的上一个值传递到 “RepNotify” 调用中。


    —— 虚幻引擎官方文档《复制 Actor 属性》

    /*** BlasterCharacter.h ***/
    UCLASS()
    class BLASTER_API ABlasterCharacter : public ACharacter
    {
    	GENERATED_BODY()
    
    	...
    
    private:
    	
    	...
    
    	/* P38 变量复制(Variable Replication)*/
    	// UPROPERTY(Replicated)
    	// class AWeapon* OverlappingWeapon;
    	UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)	// 指定 OverlappingWeapon 的 Repnotify 函数为 OnRep_OverlappingWeapon()
    	class AWeapon* OverlappingWeapon;
    
    	UFUNCTION()
    	void OnRep_OverlappingWeapon(AWeapon* LastWeapon);		// OverlappingWeapon 的 Repnotify 函数
    	/* P38 变量复制(Variable Replication)*/
    
    /*** BlasterCharacter.cpp ***/
    
    ...
    
    // 武器重叠属性 Repnotify 函数
    /* P38 变量复制(Variable Replication)*/
    void ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
    {
    	// 如果人物角色与武器发生重叠,则显示拾取组件
    	// 如果人物角色与重叠的武器重叠结束,OverlappingWeapon 将会被设置为空指针,下面的语句将不会执行 
    	if (OverlappingWeapon) {
    		OverlappingWeapon->ShowPickupWidget(true);	// 设置拾取组件可见
    	}
    
    	// 传递参数 LastWeapon 与属性 OverlappingWeapon 类型相同,复制系统会自动将属性 OverlappingWeapon 发生变化前的上一个值保存在 LastWeapon 中
    	// 也就是说如果人物角色与重叠的武器重叠结束,OverlappingWeapon 的值在被设置为空指针之前,LastWeapon 中就已经保存了发生变化前 OverlappingWeapon 的值(不为空指针),下面的语句将被执行
    	// 而当人物角色与再次与武器重叠时,OverlappingWeapon 的值在被设置为武器类实例之前,LastWeapon 中就已经保存了发生变化前 OverlappingWeapon 的值(空指针),下面的语句将不被执行
    	if (LastWeapon) {
    		LastWeapon->ShowPickupWidget(false);		// 设置拾取组件不可见
    	}
    }
    /* P38 变量复制(Variable Replication)*/
    
  3. 38.2 创建 Repnotify 函数 步骤 3,由于属性复制是单向的,只能以从服务器复制到客户端,而不能从客户端复制到服务器上,因此服务器不会调用 “Repnotify“ 函数。由于我们修改了 “Repnotify“ 函数的代码,因此还需重新单独处理服务器上设置重叠武器和显示拾取组件的代码;否则,当控制服务器上的人物角色移动至与武器结束重叠时,拾取组件仍然可见。

    /*** BlasterCharacter.cpp ***/
    
    ...
    
    // 设置与人物角色重叠的武器实例类到属性 OverlappingWeapon 中,并设置拾取组件可见
    void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
    {
    	// 在服务器上,当人物角色与武器结束重叠时调用 SetOverlappingWeapon(nullptr),此时 OverlappingWeapon 的值为发生重叠的武器类实例,先执行下面的 if 语句块隐藏拾取组件,然后再设置其值为空指针
    	// 在服务器上,当人物角色再次与武器开始重叠时调用 SetOverlappingWeapon(Weapon),此时 OverlappingWeapon 的值为空指针,下面的 if 语句块将不被执行,直接设置其值为重叠的武器类实例
    	if (OverlappingWeapon) {						
    		OverlappingWeapon->ShowPickupWidget(false);
    	}
    
    	OverlappingWeapon = Weapon;
    
    	// 使用 IsLocallyControlled() 可以快速判断当前 Pawn 是否为自己是否为主控端,即本地控制的人物(而不是镜像人物)
    	if (IsLocallyControlled()) {
    		if (OverlappingWeapon) {
    			OverlappingWeapon->ShowPickupWidget(true);	// 还是需要先判断 OverlappingWeapon 是否为空指针,如果不判断人物角色与武器结束重叠时 UE 会闪退
    		}
    	}
    }
    ...
    
  4. 编译后进行测试,可以看到我们无论是控制客户端还是服务器上的人物角色移动至与武器结束重叠,拾取组件都会变得不可见。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述


38.4 Summary

本节课为了在客户端上实现人物角色与武器开始重叠时显示拾取组件的功能,我们学习了属性复制与 “RepNotify” 函数、网络同步实现的基本原理。我们通过为 “BlasterCharacter” 添加带有 “Replicated” 说明符的 “OverlappingWeapon” 属性,来表示与人物角色重叠的武器类实例,接着重写 “GetLifetimeReplicatedProps()” 函数,在函数中我们调用了 “DOREPLIFETIME” 宏,用于指定需要被复制的属性;然后直接在 “BlasterCharacter” 的 “Tick()“ 函数中直接调用显示拾取组件的函数,但这会导致所有客户端和服务器同步显示拾取组件,而我们只想在人物角色与武器重叠的这个客户端上显示。我们尝试将 “DOREPLIFETIME()” 宏替换至 “DOREPLIFETIME_CONDITION()” 宏,虽然可以其他客户端不显示拾取组件,但服务器端仍会显示。
此时,就需要使用到 “Repnotify“ 函数,它可以在指定的属性发生复制时被调用以执行特定操作,我们首先创建了 “OnRep_OverlappingWeapon()” 这个不带任何输入参数的 “RepNotify” 函数:该函数在属性复制时自动触发,配合 “IsLocallyControlled()” 检测当前是否为主控端可精准控制提示显示范围:当客户端角色与武器重叠时,仅当前控制客户端显示提示;因为属性复制是单向的,只能以从服务器复制到客户端,而不能从客户端复制到服务器上,因此服务器不会调用 “Repnotify“ 函数,因此我们还通过在 “SetOverlappingWeapon()” 函数中添加本地控制检测来单独处理服务器显示逻辑。
为解决结束重叠时的拾取组件隐藏的功能,我们在武器类中定义了重叠结束函数,并将其动态绑定了 “OnComponentEndOverlap()” 的委托上,在函数中我们调用 “SetOverlappingWeapon(nullptr)”,即设置重叠武器为空指针。接下来就是本节课最耐人寻味的地方:我们巧妙地利用 “RepNotify” 函数的特性:为 “OnRep_OverlappingWeapon()” 函数加入与 “OverlappingWeapon” 类型相同的输入参数 “LastWeapon”,虚幻引擎的复制系统会自动将复制属性的上一个值传递到 “RepNotify” 函数调用中,这让我们能同时处理新旧重叠武器的显隐状态切换,完美解决服务器端结束重叠时的拾取组件无法隐藏的问题。
在这里插入图片描述


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

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

相关文章

AWS API Gateway配置日志

问题 访问API Gateway接口出现了403问题&#xff0c;具体报错如下&#xff1a; {"message":"Missing Authentication Token"}需要配置AWS API Gateway日志&#xff0c;看请求过程是什么样子的。 API Gateway 先找到API Gateway的的日志角色&#xff0c…

Towards Open World Object Detection概述(论文)

论文&#xff1a;https://arxiv.org/abs/2103.02603 代码&#xff1a;https://github.com/JosephKJ/OWOD Towards Open World Object Detection 迈向开放世界目标检测 Abstract 摘要 Humans have a natural instinct to identify unknown object instances in their environ…

轻松备份和恢复 Android 系统 | 4 种解决方案

我们通常会在 Android 手机上存储大量重要的个人数据&#xff0c;包括照片、视频、联系人、信息等等。如果您不想丢失宝贵的数据&#xff0c;可以备份 Android 数据。当您需要访问和使用这些数据时&#xff0c;可以将其恢复到 Android 设备。如果您想了解 Android 备份和恢复&a…

具备强大的数据处理和分析能力的智慧地产开源了

智慧地产视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。 AI是新形势下数…

【iSAQB软件架构】软件架构中构建块的视图:黑箱、灰箱和白箱及其交互机制

在软件架构描述中&#xff0c;黑箱视图&#xff08;Black-box&#xff09;、灰箱视图&#xff08;Gray-box&#xff09;和白箱视图&#xff08;White-box&#xff09; 是不同抽象层级的构建模块表示方式&#xff0c;用于满足不同受众和设计阶段的需求。以下是基于ISAQB标准的清…

如何在电脑上轻松访问 iPhone 文件

我需要将 iPhone 下载文件夹中的文件传输到 Windows 11 电脑上。我该怎么做&#xff1f;我可以在 Windows 11 上访问 iPhone 下载吗&#xff1f; 由于 iOS 和 Windows 系统之间的差异&#xff0c;在 PC 上访问 iPhone 文件似乎颇具挑战性。然而&#xff0c;只要使用正确的工具…

C语言字符数组输入输出方法大全(附带实例)

在 C语言中&#xff0c;字符数组是一种特殊的数组&#xff0c;用于存储和处理字符串。理解字符数组的输入和输出操作对于初学者来说至关重要&#xff0c;因为这是处理文本数据的基础。 字符数组的定义与初始化 在讨论输入输出之前&#xff0c;我们先来回顾一下字符数组的定义…

短视频矩阵SaaS系统:开源部署与核心功能架构指南

一、系统架构概述 短视频矩阵系统是基于SaaS&#xff08;软件即服务&#xff09;模式的多平台内容管理解决方案&#xff0c;通过开源技术实现账号聚合、智能创作、跨平台分发及数据闭环。系统采用微服务架构&#xff0c;支持高并发场景下的弹性扩展。 二、核心功能模块开发逻辑…

oss:上传图片到阿里云403 Forbidden

访问图片出现403Forbidden问题&#xff0c;我们可以直接登录oss账号&#xff0c;查看对应权限是否开通&#xff0c;是否存在跨域问题

4. 数据类型

4.1 数据类型分类 分类 数据类型 说明 数值类型 BIT(M) 位类型。M指定位数&#xff0c;默认值1&#xff0c;范围1 - 64 TINYINT [UNSIGNED] 带符号的范围 -128 ~ 127&#xff0c;无符号范围0 ~ 255&#xff0c;默认有符号 BOOL 使用0和1表示真和假 SMALLINT [UNSIGNED] 带符号是…

MySQL基础(二)SQL语言、客户端工具

目录 三、SQL语言 3.1 概念 3.2 基本操作 四、客户端工具 三、SQL语言 3.1 概念 SQL&#xff08;Structured Query Language&#xff09;结构化查询语言。SQL用于对存储数据&#xff0c;更新&#xff0c;查询和管理关系型数据库的程序设计语言。 通常执行对数据库的增删改…

CppCon 2015 学习:C++ in the audio industry

实时编程&#xff08;real-time programming&#xff09;&#xff1a;音频处理对延迟极度敏感&#xff0c;要求代码必须非常高效且稳定。无锁线程同步&#xff08;lock-free thread synchronization&#xff09;&#xff1a;避免阻塞&#xff0c;提高性能&#xff0c;尤其是在多…

C++算法-动态规划2

第 4 题 字符串分割 (Word Break) 难度: Medium备注&#xff1a;出自 leetcode题目描述 Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words. For example, given s "l…

软信天成:数据驱动型背后的人工智能,基于机器学习的数据管理

在数字化转型浪潮中&#xff0c;当代企业如同逆水行舟&#xff0c;不进则退。无数企业希望通过数字化转型捕获全新的市场机遇&#xff0c;改善财政状况&#xff0c;在未来市场竞争中占据一席之地。要想获得成功的数字化转型&#xff0c;关键因素在于具备可靠、及时的数据用以支…

MySQL提升

事务 事务&#xff1a;在多个操作合在一起视为一个整体。要么就不做、要么就做完。 事务应该满足ACID A : 原子性。不可分割。C : 一致性。追求的目标&#xff0c;在开始到结束没有发生预定外的情况。I : 隔离性。不同的事务是独立的。D : 持久性。系统崩溃&#xff0c;数据依然…

go语言的锁

本篇文章主要讲锁&#xff0c;主要会涉及go的sync.Mutex和sync.RWMutex。 一.锁的概念和发展 1.1 锁的概念 所谓的加锁和解锁其实就是指一个数据是否被占用了&#xff0c;通过Mutex内的一个状态来表示。 例如&#xff0c;取 0 表示未加锁&#xff0c;1 表示已加锁&#xff…

VUE解决页面请求接口大规模并发的问题(请求队列)

方案1&#xff1a; 请求队列 // RequestQueue.js export default class RequestQueue {constructor(maxConcurrent) {this.maxConcurrent maxConcurrent; // 最大并发请求数this.currentConcurrent 0; // 当前并发请求数this.queue []; // 请求队列this.requestId 0; // …

IDEA安装迁移IDEA配置数据位置

需求 因为C盘有清空风险&#xff0c;需要把IDEA&#xff08;2025&#xff09;安装位置以及配置数据都挪到D盘。 安装 到官网下载安装包 安装&#xff0c;这里可以改下安装位置 这几个选项随意&#xff0c;然后一直下一步就好 完成后重启或不重启都随意 迁移数据 初次安…

Blazor-表单提交的艺术:如何优雅地实现 (下)

在上一章节中我们使用HTML的方式介绍了如何在Blazor框架下进行表单的提交&#xff0c;而在Blazor框架中也为我们内置了<EditForm>组件来代替原始的HTML,<form>&#xff0c;下面我们将对<EditForm>的用法进行讲解&#xff0c;并将两种表单方式进行对比&#x…

五子棋网络对战游戏的设计与实现设计与实现【源码+文档】

五子棋网络对战游戏的设计与实现 摘 要 在现代社会中,及其它无线设备越来越多的走进普通老百姓的工作和生活。随着3G技术的普及与应用&#xff0c;基于Java开发的软件在上的使用非常的广泛&#xff0c;增值服务的内容也是越来越多&#xff0c;对丰富人们的生活内容、提供快…