在上一篇文章里,我们实现了添加负面效果GE,并且在添加GE时,也会给角色应用一个负面效果标签作为标识。在这一篇里,我们将通过负面效果标签标识,应用角色身上展现对应的负面效果的表现。
我们将在这篇文章里添加一个自定义的Niagara组件,来实现对应的负面效果表现,并通过观察角色身上是否应用的对应的标签,来激活和取消激活组件。

解决应用标签的bug

首先我们解决之前编写的通过代码设置GE标签的方法。
和之前的相比,有两处问题,第一处就是不应该直接获取,而是直接创建一个新的FInheritedTagContainer。
第二处是在添加的地方需要设置到Added对象上,对应UE里设置GE。

UTargetTagsGameplayEffectComponent& TargetTagsGameplayEffectComponent = Effect->AddComponent<UTargetTagsGameplayEffectComponent>();
FInheritedTagContainer InheritableOwnedTagsContainer; //创建组件所需的标签容器
InheritableOwnedTagsContainer.Added.AddTag(DeBuffType); //添加标签
TargetTagsGameplayEffectComponent.SetAndApplyTargetTagChanges(InheritableOwnedTagsContainer); //应用并更新
  • 1.
  • 2.
  • 3.
  • 4.

创建自定义的Niagara组件类

为了能够自定义负面效果表现的自动开关功能,我们需要自己扩展Niagara类,在里面增加内容。

我们基于NiagaraComponent创建一个派生类

93. UE5 GAS RPG 应用负面效果表现_ue5

设定好相关名称和目录

93. UE5 GAS RPG 应用负面效果表现_自定义_02

在派生类里,我们添加构造函数,设置标识当前对应的负面效果的标签,以及对应标签变动回调以及角色死亡回调。

UCLASS()
class RPG_API UDeBuffNiagaraComponent : public UNiagaraComponent
{
	GENERATED_BODY()

public:

	UDeBuffNiagaraComponent();
	
	UPROPERTY(EditDefaultsOnly)
	FGameplayTag DeBuffTag; //用来标识粒子系统的标签

protected:
	virtual void BeginPlay() override; //覆写开始运行
	void DeBuffTagChanged(const FGameplayTag CallbackTag, int32 NewCount); //当前的负面标签变动回调

	UFUNCTION()
	void OnOwnerDeath(AActor* DeadActor); //在角色死亡时的回调
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

在构造函数中,我们将自动激活关闭

UDeBuffNiagaraComponent::UDeBuffNiagaraComponent()
{
	bAutoActivate = false; //关闭自动激活
}
  • 1.
  • 2.
  • 3.
  • 4.

在事件开始执行时,我们绑定对应的回调,用来检测角色的变动,并在变动时触发回调

void UDeBuffNiagaraComponent::BeginPlay()
{
	Super::BeginPlay();

	ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetOwner()); //获取到战斗接口
	//通过函数库获取角色身上的ASC
	if(UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwner()))
	{
		//监听负面标签变动回调
		ASC->RegisterGameplayTagEvent(DeBuffTag, EGameplayTagEventType::NewOrRemoved).AddUObject(this, &UDeBuffNiagaraComponent::DeBuffTagChanged);
	}
	else if(CombatInterface) //如果绑定时,ASC未初始化成功,则监听ASC创建完成委托,完成对负面标签的监听
	{
		//AddWeakLambda 这种绑定方式的主要好处是,当绑定的对象被销毁时,委托不会保持对象的引用,从而避免悬空指针问题和内存泄漏。
		CombatInterface->GetOnASCRegisteredDelegate().AddWeakLambda(this,[this](UAbilitySystemComponent* InASC)
		{
			//监听负面标签变动回调
			InASC->RegisterGameplayTagEvent(DeBuffTag, EGameplayTagEventType::NewOrRemoved).AddUObject(this, &UDeBuffNiagaraComponent::DeBuffTagChanged);
		});
	}
	//绑定死亡后销毁
	if(CombatInterface)
	{
		CombatInterface->GetOnDeathDelegate().AddDynamic(this, &UDeBuffNiagaraComponent::OnOwnerDeath);
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

监听负面效果标签的回调里,我们可以获取角色身上对应标签的个数,如果不为0,则激活粒子

void UDeBuffNiagaraComponent::DeBuffTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
	if(NewCount > 0)
	{
		Activate(); //绑定的负面标签大于0,激活特效
	}
	else
	{
		Deactivate(); //没有对应标签,关闭激活
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

在触发死亡回调时,关闭激活

void UDeBuffNiagaraComponent::OnOwnerDeath(AActor* DeadActor)
{
	Deactivate();
}
  • 1.
  • 2.
  • 3.
  • 4.

添加对应委托

在自定义Niagara组件里,我们需要两个委托来监听角色身上的负面标签变动以及角色是否死亡
在战斗接口类里,我们增加两个委托,可以通过其进行绑定
Dynamic的区别在于能不能绑定蓝图,非Dynamic的无法绑定,Dynamic的需要通过UE反射绑定,也可以直接蓝图获取绑定。

DECLARE_MULTICAST_DELEGATE_OneParam(FOnASCRegistered, UAbilitySystemComponent*); //Actor初始化ASC完成后委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDeath, AActor*, DeadActor); //Actor死亡后的委托
  • 1.
  • 2.

然后我们在类里增加两个获取函数,可以获取对应的委托,这里我们需要返回引用

virtual FOnASCRegistered& GetOnASCRegisteredDelegate() = 0; //获取ASC注册成功后的委托
	virtual FOnDeath& GetOnDeathDelegate() = 0; //获取死亡委托
  • 1.
  • 2.

接着,我们在角色基类里增加两个对应的委托

FOnASCRegistered OnASCRegistered; //ASC注册成功委托
	FOnDeath OnDeath; //角色死亡后触发的死亡委托
  • 1.
  • 2.

覆写两个函数

virtual FOnASCRegistered& GetOnASCRegisteredDelegate() override; //获取ASC注册成功委托
	virtual FOnDeath& GetOnDeathDelegate() override; //角色死亡委托
  • 1.
  • 2.

对其实现一下,返回对应的委托

FOnASCRegistered& ARPGCharacter::GetOnASCRegisteredDelegate()
{
	return OnASCRegistered;
}

FOnDeath& ARPGCharacter::GetOnDeathDelegate()
{
	return OnDeath;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

触发委托

现在我们创建完成了对应的委托,还需要在合适的位置对其进行广播
首先是ASC创建完成后的广播,我选择在InitAbilityActorInfo函数里,创建完成ASC后广播

void ARPGEnemy::InitAbilityActorInfo()
{
	AbilitySystemComponent->InitAbilityActorInfo(this, this);
	Cast<URPGAbilitySystemComponent>(AbilitySystemComponent)->AbilityActorInfoSet();

	//通过GE初始角色的属性
	if(HasAuthority())
	{
		InitializeDefaultAttributes();
	}

	//调用ASC广播
	OnASCRegistered.Broadcast(AbilitySystemComponent);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

角色的函数内容不同,在函数执行完时,也完成了广播

void ARPGHero::InitAbilityActorInfo()
{
	ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	//从playerState获取ASC和AS
	AbilitySystemComponent = PlayerStateBase->GetAbilitySystemComponent();
	AttributeSet = PlayerStateBase->GetAttributeSet();
	//初始化ASC
	AbilitySystemComponent->InitAbilityActorInfo(PlayerStateBase, this);
	//触发Actor的技能信息设置回调
	Cast<URPGAbilitySystemComponent>(AbilitySystemComponent)->AbilityActorInfoSet(); 

	//获取PC
	if(ARPGPlayerController* PlayerControllerBase = Cast<ARPGPlayerController>(GetController()))
	{
		if(ARPGHUD* HUD = Cast<ARPGHUD>(PlayerControllerBase->GetHUD()))
		{
			HUD->InitOverlay(PlayerControllerBase, PlayerStateBase, AbilitySystemComponent, AttributeSet);
		}
	}

	//通过GE初始角色的属性
	InitializeDefaultAttributes();

	//调用ASC广播
	OnASCRegistered.Broadcast(AbilitySystemComponent);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

然后就是角色的死亡委托广播,我们之前创建了一个多播函数,在角色死亡后,每个游戏端都会触发此函数

void ARPGCharacter::MulticastHandleDeath_Implementation()
{
	//播放死亡音效
	UGameplayStatics::PlaySoundAtLocation(this, DeathSound, GetActorLocation());
	
	//开启武器物理效果
	Weapon->SetSimulatePhysics(true); //开启模拟物理效果
	Weapon->SetEnableGravity(true); //开启重力效果
	Weapon->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); //开启物理碰撞通道

	//开启角色物理效果
	GetMesh()->SetSimulatePhysics(true); //开启模拟物理效果
	GetMesh()->SetEnableGravity(true); //开启重力效果
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); //开启物理碰撞通道
	GetMesh()->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); //开启角色与静态物体产生碰撞

	//关闭角色碰撞体碰撞通道,避免其对武器和角色模拟物理效果产生影响
	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	//设置角色溶解
	Dissolve();

	//设置死亡状态
	bDead = true;
	
	//触发死亡委托
	OnDeath.Broadcast(this);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

在蓝图编辑

接着,我们在角色积累增加一个Niagara组件,设置火的负面效果

UPROPERTY(VisibleAnywhere) //火焰负面效果表现组件
	TObjectPtr<UDeBuffNiagaraComponent> BurnDeBuffComponent;
  • 1.
  • 2.

在构造函数里初始化,并设置好对应的负面标签

//初始化火焰负面效果组件
	BurnDeBuffComponent = CreateDefaultSubobject<UDeBuffNiagaraComponent>("BurnDeBuffComponent");
	BurnDeBuffComponent->SetupAttachment(GetRootComponent());
	BurnDeBuffComponent->DeBuffTag = FRPGGameplayTags::Get().DeBuff_Burn; //设置匹配的负面标签
  • 1.
  • 2.
  • 3.
  • 4.

编译打开UE,可以看到,里面增加了对应的组件

93. UE5 GAS RPG 应用负面效果表现_初始化_03

我们可以在组件上设置使用的Niagara资产

93. UE5 GAS RPG 应用负面效果表现_初始化_04

还可以修改对应的DeBuff标签

93. UE5 GAS RPG 应用负面效果表现_android_05

实现完成后,我们需要进行测试,测试的主要内容是

  1. 在激活负面效果时,是否能够正确激活Niagara
  2. 在负面效果结束时,标签是否能够在角色身上正确清除,Niagara是否能够自动结束
  3. 在激活负面效果时,角色死亡是否能够自动停止激活等待销毁。
    如果都没有问题,基本上效果实现完毕。