[UE笔记]Weapon Class (1)

听课笔记

Weapon类初步主体

实现了该类的基础设置

建立一个继承自Actor的C++类,放在相应的文件夹中。按项目结构梳理一下,应当放在与GameModeHUD同级的地方,建立一个新的文件夹,以备后续派生出各种具体的武器,都放在这个文件夹里。

在这里插入图片描述

这个Weapon类也不是最终的Weapon类,后续会派生出射弹武器类和扫描武器类。此处仅实现通用的功能即可。

Weapon类基础代码

在这里插入图片描述

随后蓝图实例化配置一下网格体和碰撞检测球体就好了。主要代码如下

// header file
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"

UENUM(BlueprintType)
enum class EWeaponState : uint8
{
	EWS_Initial UMETA(DisplayName = "Initial State"),
	EWS_Equipped UMETA(DisplayName = "Equipped"),
	EWS_Dropped UMETA(DisplayName = "Dropped"),

	EWS_MAX UMETA(DisplayName = "DefaultMAX")
};

UCLASS()
class BLASTER_API AWeapon : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AWeapon();
	virtual void Tick(float DeltaTime) override;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

private:
	// visiable anywhere so can be seen in blueprint
	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	USkeletalMeshComponent* WeaponMesh;

	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	class USphereComponent* AreaSphere;

	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	EWeaponState WeaponState;
};
// cpp file
// Fill out your copyright notice in the Description page of Project Settings.


#include "Weapon.h"
#include "Components/SphereComponent.h"
#include "Components/WidgetComponent.h"
#include "Blaster/Character/BlasterCharacter.h"

// Sets default values
AWeapon::AWeapon()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	
	bReplicates = true;
	
	WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMeesh"));
	WeaponMesh->SetupAttachment(RootComponent);
	//SetRootComponent(WeaponMesh);
	
	WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
	WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
	WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	
	// this component is what we use to detected overlap with players
	// Once they overlap, we want to pickup weapon
	AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere"));
	AreaSphere->SetupAttachment(RootComponent);
	AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	// only enabled on server
	AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	
}

// Called when the game starts or when spawned
void AWeapon::BeginPlay()
{
	Super::BeginPlay();

	// same as if (GetLocalRole() == ENetRole::ROLE_Authority)
	if (HasAuthority())
	{
		AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
	}
	
}

// Called every frame
void AWeapon::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

关于bCanEverTick

PrimaryActorTick.bCanEverTick = false;

If false, this tick function will never be registered and will never tick. Only settable in defaults.

关于replicating actor

不太相关的参考资料ReplicateActor学习笔记

同步Actor是网络游戏要考虑的一个问题,因为其状态和相关操作应当以服务器的权威版本为主。

为了将武器设为同步的,使用了下方代码

// in constructor
bReplicates = true;

该部分内容亦在官方文档中有讨论,文档连接中的“Actor复制”小节即是,由于其内容详尽,此处不再多言。

关于SetRootComponent

我不知道教程为什么前半截讲weapon.cpp时出现了这个SetRootComponent(WeaponMesh);,但是最后这节课github提交中并未出现。事实上代码中存在这行语句会导致编辑器无法启动:

在这里插入图片描述

最直观的原因便是该函数要求传入的参数类型是USceneComponent,而WeaponMesh的类型是USkeletalMeshComponent,显然不符合。

bool SetRootComponent
(
    USceneComponent * NewRootComponent
)

关于RootComponent,它是定义在Actor.h的变量(所以不用重新声明)

The component that defines the transform (location, rotation, scale) of this Actor in the world, all other components must be attached to this one somehow


一般对RootComponent的使用方式就是在.h文件中定义:

UPROPERTY(EditAnywhere)
USceneComponent* OurVisibleComponent;

该变量标记为 UPROPERTY,因此其将对 虚幻引擎 可见。此设置可防止启动游戏时,或项目/关卡关闭后重新载入时重设该变量。

然后在相应的.cpp文件中使用:

// 创建可附加内容的虚拟根组件。
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
// 创建相机和可见对象
UCameraComponent* OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("OurCamera"));
OurVisibleComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("OurVisibleComponent"));
// 将相机和可见对象附加到根组件。偏移并旋转相机。
OurCamera->SetupAttachment(RootComponent);
OurCamera->SetRelativeLocation(FVector(-250.0f, 0.0f, 250.0f));
OurCamera->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
OurVisibleComponent->SetupAttachment(RootComponent);

关于CollisionResponse

参考官方文档碰撞响应参考

WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);

注意第三条语句,只有在启用碰撞(Collision Enabled)设为无碰撞(No Collision)以外的值才能进一步决定碰撞响应(Collision Responses)的策略。也就是只有“能碰撞”后才能决定“如何碰撞”

并且碰撞事件分为两种:重叠(Overlap)阻挡(Block)

为了让武器丢在地上后能够被角色越过去,所以才有的前两条设置,但是一开始武器不是丢在地上的状态,所以干脆直接都设为ECollisionEnabled::NoCollision,待后续依据武器状态再改变。

在这里插入图片描述

武器两个主要部分,一个是描述武器样子的网格体,另一个是用于和人物碰撞(产生重叠事件意味着可以拾起)

Overlap Event与服务器

为了安全性,武器仅在服务端启用Overlap Event。

AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);

在随后的BeginPlay中鉴定是否是服务端,如果是则启用。

// same as if (GetLocalRole() == ENetRole::ROLE_Authority)
if (HasAuthority())
{
	AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}

服务器掌管所有武器对象,并且为其处理重叠事件。

关于UENUM

官方文档:元数据说明符

声明类、接口、结构体、列举、列举值、函数,或属性时,可添加元数据说明符来控制其与引擎和编辑器各方面的相处方式。每一种类型的数据结构或成员都有自己的元数据说明符列表。

要添加元数据说明符,需使用单词meta,后接说明符列表。如有必要,可以将它们的值添加到 UCLASSUENUMUINTERFACEUSTRUCTUFUNCTIONUPROPERTY宏,如下所示:

{UCLASS/UENUM/UINTERFACE/USTRUCT/UFUNCTION/UPROPERTY}(SpecifierX, meta=(MetaTag1="Value1", MetaTag2, ..), SpecifierY)

要添加元数据说明符到列举值,可将UMETA标签添加到值本身。如果存在用于分隔的逗号,则要添加到逗号之前,如下所示:

UENUM()
enum class EMyEnum : uint8
{
    // Default Value Tooltip
    DefaultValue = 0 UMETA(MetaTag1="Value1", MetaTag2, ..),

    // ValueWithoutMetaSpecifiers Tooltip
    ValueWithoutMetaSpecifiers,

    // ValueWithMetaSpecifiers Tooltip
    ValueWithMetaSpecifiers UMETA((MetaTag1="Value1", MetaTag2, ..),

    // FinalValue Tooltip
    FinalValue (MetaTag1="Value1", MetaTag2, ..)
};

对比自己代码中的内容不难理解其含义

UENUM(BlueprintType)
enum class EWeaponState : uint8
{
	EWS_Initial UMETA(DisplayName = "Initial State"),
	EWS_Equipped UMETA(DisplayName = "Equipped"),
	EWS_Dropped UMETA(DisplayName = "Dropped"),

	EWS_MAX UMETA(DisplayName = "DefaultMAX")
};

武器的拾取:服务端部分

提示控件的基本设置

HUD文件夹中

一方面是控件的制作,本身很简单,主要就是一个text block

在这里插入图片描述

因为这个控件是外部的,需要为Weapon添加控件,一个指向它的指针,代码如下:

UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
class UWidgetComponent* PickupWidget;

在这里插入图片描述

另一方面是委托的绑定,可见下文重叠事件部分。

基础代码

在这里插入图片描述

如上图所示,在原先代码基础上,增加了一些内容,主要是一个UWidgetComponent

// Weapon.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"

UENUM(BlueprintType)
enum class EWeaponState : uint8
{
	EWS_Initial UMETA(DisplayName = "Initial State"),
	EWS_Equipped UMETA(DisplayName = "Equipped"),
	EWS_Dropped UMETA(DisplayName = "Dropped"),

	EWS_MAX UMETA(DisplayName = "DefaultMAX")
};

UCLASS()
class BLASTER_API AWeapon : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AWeapon();
	virtual void Tick(float DeltaTime) override;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UFUNCTION()
	virtual void OnSphereOverlap(
		UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult
	);

private:
	// visiable anywhere so can be seen in blueprint
	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	USkeletalMeshComponent* WeaponMesh;

	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	class USphereComponent* AreaSphere;

	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	EWeaponState WeaponState;
	
	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
	class UWidgetComponent* PickupWidget;
};

// Weapon.cpp
#include "Weapon.h"
// Fill out your copyright notice in the Description page of Project Settings.


#include "Weapon.h"
#include "Components/SphereComponent.h"
#include "Components/WidgetComponent.h"
#include "Blaster/Character/BlasterCharacter.h"

// Sets default values
AWeapon::AWeapon()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	
	bReplicates = true;
	
	WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMeesh"));
	//WeaponMesh->SetupAttachment(RootComponent);
	SetRootComponent(WeaponMesh);
	
	WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
	WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
	WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	
	// this component is what we use to detected overlap with players
	// Once they overlap, we want to pickup weapon
	AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere"));
	AreaSphere->SetupAttachment(RootComponent);
	AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	// only enabled on server
	AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	
	PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget"));
	PickupWidget->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AWeapon::BeginPlay()
{
	Super::BeginPlay();

	// same as if (GetLocalRole() == ENetRole::ROLE_Authority)
	if (HasAuthority())
	{
		AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
	}
}

void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}

// Called every frame
void AWeapon::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

重叠事件

有关碰撞,可先参阅如下官方文档:

碰撞概述

两个Actor都需要设置为阻挡彼此相应的对象类型。如果不这样设置,就不会发生碰撞。

(上述内容与正文无关)


重点来看OnSphereOverlap这个函数,我们可在这个函数中设置控件可见性:

// in Weapon.h
UFUNCTION()
virtual void OnSphereOverlap(
	UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult
);

该函数应当只在服务端调用,故而添加在HasAuthority()的if检验语句内下列内容:

// Weapon.cpp中的HasAuthority的if语句体内
AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);

另一方面,我们要把它绑定到AeraSphereOnComponentBeginOverlap委托上。这里就要解释了为什么该函数必须有这些参数:

在这里插入图片描述
查看OnComponentBeginOverlap的定义,得知它是FComponentBeginOverlapSignature类型的委托,再看他的定义

DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams( 
	FComponentBeginOverlapSignature, 
	UPrimitiveComponent, OnComponentBeginOverlap, 
	UPrimitiveComponent*, OverlappedComponent, 
	AActor*, OtherActor, 
	UPrimitiveComponent*, OtherComp, 
	int32, OtherBodyIndex, 
	bool, bFromSweep, 
	const FHitResult &, SweepResult);

对于其实现,使用了如下代码:

ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
if (BlasterCharacter && PickupWidget)
{
	PickupWidget->SetVisibility(true);
}

意思是,如果重叠的那个物体,能够转换为ABlasterCharacter类型,换言之重叠的物体是人物类(在本文即ABlasterCharacter,这个人物类名称只是这个项目的叫法,在别的项目可能有别的名字)的实例,或者说人物与之发生了重叠,才可能有后续操作。

当然,一开始这个控件应当是不显示的,所以BeginPlay函数中应当加上下面语句,让它不具有可见性

if (PickupWidget)
{
	PickupWidget->SetVisibility(false);
}

接下来需要解决客户端上的控件显示问题,因为重叠事件只在服务端产生,客户端是不知道的。因此这就涉及到复制(Replication),我们需要一个状态,客户端可以从服务端同步这个状态,以进行显示控件等操作。

武器的拾取:客户端部分

变量复制

C++:使用UPROPERTY宏进行标记。仅客户端执行,服务端也要执行一些功能可以考虑使用RPC

蓝图:细节面板中指定。服务端和客户端都可以执行。


格式:

UPROPERTY(ReplicatedUsing=xxx)

Rep Notify

  1. 某些变量(如生命值)由于安全性,应当仅在服务端进行修改,客户端进行同步
  2. 更新值之后可指定一个调用的函数,该函数就是一个Rep Notify
  3. Rep Notify仅在变量更新时本地触发
  4. Rep Notify比RPC节约带宽(显而易见地…)
  5. 用途举例:UI的修改(比如在更新生命值之后修改血量UI)

当然,接上文,ReplicatedUsing=xx这里的函数xxx应当使用UFUNCTION()修饰,哪怕该宏内部没有任何东西。

蓝图变量设为RepNotify后自动产生对应的Rep Notify,而C++需要手动设置

局部代码逻辑

  1. 声明复制的变量
  2. 将之注册为要复制的
  3. 声明更新时要调用的RepNotify
  4. 实现该RepNotify的具体功能

在这里插入图片描述

这里代码的逻辑也很明确了,由于overlap的主动发起者是玩家,所以在玩家的头文件中声明那个要复制的变量:

// .h private section
UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
class AWeapon* OverlappingWeapon;
// .cpp defination
void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(ABlasterCharacter, OverlappingWeapon);
}

其逻辑是:有一个重叠的武器,如果重叠就更新这个类型为AWeapon*的指针,当它不为NULL时则意味着人物与某个武器重叠,此时应当显示控件。当然,这里没考虑重叠多个武器的情况。

我们在GetLifetimeReplicatedProps函数中将变量OverlappingWeapon通过DOREPLIFETIME宏注册为replicated,该宏的第一个参数是该变量所在类的名称。

为了使用该宏,应当引入头文件#include "Net/UnrealNetwork.h"

ShowPickupWidget是在玩家的Tick()中调用的,这不是高效的,后面会进行修改。

在这里插入图片描述

为什么是玩家调用武器的该函数,而非武器直接调用,是因为主被动关系。如果武器是主动的,那么会出现别的玩家重叠之后在本机显示控件的情况。而我们期望的只是我这个客户端,在服务端发生了overlap事件,然后玩家因为Weapon的Overlap事件

修复1:仅在当前客户端有效

上面的问题是所有的客户端都能看见这个控件,我们应当确保参与Overlap事件的客户端是本机所拥有的才显示控件。

但是这样的复制不完全有意义:复制给其他没有Overlap的客户端,它们用不到,还占带宽。

所以只复制给拥有该角色(指参与Overlap的角色)的客户端是最理想的。

因此DOREPLIFETIME宏就得被修改成另一种宏,以确保该属性仅发送至 actor 的所有者。

DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly);

也就是你这台机器有该pawn的所有权,或者说你控制着这个pawn,再换句话说就是you are the owner of the pawn,在这种情况下服务器应当复制给作为客户端的你这台机器。这里的宏观个体是机器,因为要从网络通信层面考虑。

更进一步地,相关内容可以参阅官方文档:条件属性复制

修复2:服务端只显示自己的

上方修复完之后,客户端确实只在自己控制的角色和武器重叠之后才显示控件。但是服务端是无论如何都会显示的。我们希望服务端也只当自己控制的pawn。所以这个显示不应当在Tick中进行。这时候就用到了上面提到了RepNotify。

RepNotify函数的惯例是以OnRep_开头后接要复制的变量的名称。

这类函数不具有参数,因为是自动调用的。且应当用UFUNCTION修饰。另外要为复制变量指明其RepNotify

// in BlasterCharacter.h's private section
UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)
class AWeapon* OverlappingWeapon;

UFUNCTION()
void OnRep_OverlappingWeapon(AWeapon* LastWeapon);

并且在BlasterCharacter.cpp中进行OnRep_OverlappingWeapon的定义:

void ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
{
	if (OverlappingWeapon)
	{
		OverlappingWeapon->ShowPickupWidget(true);
	}
}

很好,在上述进行完后,服务器成功不显示任何控件了

由于复制是单向的,即服务端到客户端,所以RepNotify只发生在客户端。

服务端由于不会调用RepNotify,故而无法显示自己的控件。所以我们需要处理我们操作的机器是服务端的情况。

所以我们要为OverlappingWeaponSetter添加新功能,先修改其声明:

//FORCEINLINE void SetOverlappingWeapon(AWeapon* Weapon) { OverlappingWeapon = Weapon; }
void SetOverlappingWeapon(AWeapon* Weapon);

并且为服务端这种情况增加新的设置控件可见性的代码

void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
{
	OverlappingWeapon = Weapon;
	if (IsLocallyControlled())
	{
		if (OverlappingWeapon)
		{
			OverlappingWeapon->ShowPickupWidget(true);
		}
	}
}

此处要说一下IsLocallyControlled(),引用一下(网络同步)不用RPC实现客户端/服务端/双端逻辑的代码。

void AThirdPersonCharacter::OnHealthUpdate()
{
    //客户端特定的功能
    if (IsLocallyControlled())  
    {...}

    //服务器特定的功能
    if (GetLocalRole() == ROLE_Authority)
    {...}

    //在所有机器上都执行的功能
    ...
}

修复3:隐藏控件

虽然解决了服务端可以显示,但是离开碰撞体范围后并不能隐藏,因此为Setter构建flipflop(即增添第一个if语句)

void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon)
{
	if (OverlappingWeapon)
	{
		OverlappingWeapon->ShowPickupWidget(false);
	}
	OverlappingWeapon = Weapon;
	if (IsLocallyControlled())
	{
		if (OverlappingWeapon)
		{
			OverlappingWeapon->ShowPickupWidget(true);
		}
	}
}

Weapon.h中的private部分添加结束重叠的事件

FUNCTION()
void OnSphereEndOverlap(
	UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex
);

以及相应的实现代码:

void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
	if (BlasterCharacter)
	{
		BlasterCharacter->SetOverlappingWeapon(nullptr);
	}
}

以及相应地在Weapon.cppBeginPlay()中的注册:

// in the body of if (HasAuthority())
AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap);

声明委托调用的函数是一回事,把它绑定到相应的委托上是另一回事。虽然这个回调长得像委托。但是委托来自于这个SphereComponent,也就是AreaSphere,找到它的OnComponentEndOverlap才对。


此时已经可以做到重叠时设置OverlappingWeapon为相关物体,结束重叠置为nullptr,相应地,RepNotify的定义也得修改,修改为如下:

void ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
{
	if (OverlappingWeapon)
	{
		OverlappingWeapon->ShowPickupWidget(true);
	}
	if (LastWeapon)
	{
		LastWeapon->ShowPickupWidget(false);
	}
}

虽说一般情况下RepNotify没参数,但是可以有,此时参数只能有一个,且必定是RepNotify执行前它对应的变量的值。

比如拥有RepNotify的变量A由1变为2,那么它的RepNotify传入的就是1。不需主动调用,自动传入变化之前的值。

当然,进入RepNotify中,A已经变成了2,但是你仍可以通过RepNotify参数中声明的局部变量来访问到值等同于A先前的值的副本。

其他

题外话,可以看看这个B站教程: 彻底掌握UE4网络-05 Rep_Notify以及这篇文章:Ue4广域网(3)----Replication和Rep_Notify

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值