虚幻四Gameplay Ability System入门(12)-Ability Task

今天就是本教程的最后一章了,前一篇当中提到,Ability的运行如果没有Task的话,实际上是在一帧内运行完成的,这肯定是不能满足实际情况的,因此我们需要Ability Task,让Ability可以在更长的时间范围内相应处理不同的动作。

我们之前持续使用的PlayMontageAndWait,WaitTargetData,Wait,WaitGameplayEvent等等功能全部都是Ability Task。既然如此,我们也会想要实现自己的技能任务,本篇教程就介绍一下实现一个简单的Ability Task的方法。

创建Ability Task

首先创建C++类,我们可以看到AbilityTask实际上是继承于GameplayTask,GameplayTask继承于虚幻四的万物之父Uobject类。

虚幻四Gameplay Ability System入门(12)-Ability Task_第1张图片

这里我打算实现一个功能,可以等待某一属性发生改变。所以命名为AT_AttributeChange。

打开生成的类,先不着急实现代码,我们先进去它的基类AbilityTask中。在这里EPIC官方写的文档已经大致说明了实现一个技能任务的要求,我这里大致讲解一下:

  1. 定义一个multicast的delegate,它需要是BlueprintAssignable的,这里的delegate会作为任务的输出,当委托被触发后,会在蓝图中被执行。这里的输出我在下图中用红圈圈出了,它实际上就是一个delegate,而传输的值就在下方。
  2. 任务的输入是通过一个static factory function进行的,相当于创建了一个当前Task的instance。输入则在该函数中定义,下图我红圈圈出的地方,我的task输入为bool, ability system component和attribute。这里官方说明尽量不要在本function的地方绑定delegate或callback。
  3. 最后一个部分是重写Activate()函数,在这里实际上执行任务的逻辑,而不要在static factory function中执行。
  4. 最后还有一个注意,就是要重写OnDestroy()函数,负责销毁任务。
/**
 *	AbilityTasks are small, self contained operations that can be performed while executing an ability.
 *	They are latent/asynchronous is nature. They will generally follow the pattern of 'start something and wait until it is finished or interrupted'
 *	
 *	We have code in K2Node_LatentAbilityCall to make using these in blueprints streamlined. The best way to become familiar with AbilityTasks is to 
 *	look at existing tasks like UAbilityTask_WaitOverlap (very simple) and UAbilityTask_WaitTargetData (much more complex).
 *	
 *	These are the basic requirements for using an ability task:
 *	
 *	1) Define dynamic multicast, BlueprintAssignable delegates in your AbilityTask. These are the OUTPUTs of your task. When these delegates fire,
 *	execution resumes in the calling blueprints.
 *	
 *	2) Your inputs are defined by a static factory function which will instantiate an instance of your task. The parameters of this function define
 *	the INPUTs into your task. All the factory function should do is instantiate your task and possibly set starting parameters. It should NOT invoke
 *	any of the callback delegates!
 *	
 *	3) Implement a Activate() function (defined here in base class). This function should actually start/execute your task logic. It is safe to invoke
 *	callback delegates here.
 *	
 *	
 *	This is all you need for basic AbilityTasks. 
 *	
 *	
 *	CheckList:
 *		-Override ::OnDestroy() and unregister any callbacks that the task registered. Call Super::EndTask too!
 *		-Implemented an Activate function which truly 'starts' the task. Do not 'start' the task in your static factory function!
 */

虚幻四Gameplay Ability System入门(12)-Ability Task_第2张图片

实现Ability Task

大致理解了AT的实现要求,接下来我们开始具体的操作过程。

首先放完整的代码,然后我再具体的讲解流程。

#pragma once

#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "AT_AttributeChange.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttributeChanged, FGameplayAttribute, Attribute, float, NewValue, float, OldValue);

/**
 * 
 */
UCLASS()
class GAS_LEARN_API UAT_AttributeChange : public UAbilityTask
{
	GENERATED_BODY()

public:
	UAT_AttributeChange(const FObjectInitializer& ObjectInitializer);
	
	virtual void Activate() override;
	
	UPROPERTY(BlueprintAssignable)
	FAttributeChanged AttributeChanged;

	UFUNCTION(BlueprintCallable, Category="Ability|Tasks", meta=(HidePin="OwningAbility", DefaultToSelf="OwningAbility", BlueprintInternalUseOnly="TRUE"))
	static UAT_AttributeChange* ListenForAttributeChange(
		UGameplayAbility* OwningAbility,
		bool TriggerOnce,
		UAbilitySystemComponent* SystemComponent,
		FGameplayAttribute Attribute);

protected:
	bool TriggerOnce;

	FGameplayAttribute Attribute;

	void OnAttributeChanged(const FOnAttributeChangeData& Data);

	virtual void OnDestroy(bool bInOwnerFinished) override;
	
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "AT_AttributeChange.h"

#include "AbilitySystemComponent.h"

UAT_AttributeChange::UAT_AttributeChange(const FObjectInitializer& ObjectInitializer)
	:Super(ObjectInitializer)
{
	TriggerOnce = false;
}

void UAT_AttributeChange::Activate()
{
	if(IsValid(AbilitySystemComponent) && Attribute.IsValid())
	{
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(this, &UAT_AttributeChange::OnAttributeChanged);
	}
	
	Super::Activate();
}

UAT_AttributeChange* UAT_AttributeChange::ListenForAttributeChange(
	UGameplayAbility* OwningAbility,
	bool TriggerOnce,
	UAbilitySystemComponent* SystemComponent,
	FGameplayAttribute Attribute)
{
	UAT_AttributeChange* MyObj = NewAbilityTask<UAT_AttributeChange>(OwningAbility);
	MyObj->TriggerOnce = TriggerOnce;
	MyObj->AbilitySystemComponent = SystemComponent;
	MyObj->Attribute = Attribute;

	return MyObj;
}

void UAT_AttributeChange::OnAttributeChanged(const FOnAttributeChangeData& Data)
{
	AttributeChanged.Broadcast(Data.Attribute, Data.NewValue, Data.OldValue);

	if(TriggerOnce)
	{
		EndTask();
	}
}

void UAT_AttributeChange::OnDestroy(bool bInOwnerFinished)
{
	if(AbilitySystemComponent)
	{
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).RemoveAll(this);
	}
	Super::OnDestroy(bInOwnerFinished);
}

创建delegate

首先先定义一个delegate,因为这里接受的是变化的属性,所以需要三个变量,属性,新值和旧值。然后在类中创建该委托,这里创建的delegate就会是任务的输出。

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttributeChanged, FGameplayAttribute, Attribute, float, NewValue, float, OldValue);

UCLASS()
class GAS_LEARN_API UAT_AttributeChange : public UAbilityTask
{
public:
	UPROPERTY(BlueprintAssignable)
	FAttributeChanged AttributeChanged;
}

创建Static factory function

这里的功能是创建Ability Task的instance,这里的函数名就是我们创建Task时实际使用的名字。函数的变量则是任务的输入。这里有个问题,就是实际上Ability Task不需要我手动赋值AbilitySystemComponent,所以这个变量可以去掉。

UFUNCTION(BlueprintCallable, Category="Ability|Tasks", meta=(HidePin="OwningAbility", DefaultToSelf="OwningAbility", BlueprintInternalUseOnly="TRUE"))
	static UAT_AttributeChange* ListenForAttributeChange(
		UGameplayAbility* OwningAbility,
		bool TriggerOnce,
		UAbilitySystemComponent* SystemComponent,
		FGameplayAttribute Attribute);

protected:
	bool TriggerOnce;
	FGameplayAttribute Attribute;

函数实现,实际上就是将输入赋值给protected中定义的变量,并不负责逻辑。

UAT_AttributeChange* UAT_AttributeChange::ListenForAttributeChange(
	UGameplayAbility* OwningAbility,
	bool TriggerOnce,
	UAbilitySystemComponent* SystemComponent,
	FGameplayAttribute Attribute)
{
	UAT_AttributeChange* MyObj = NewAbilityTask<UAT_AttributeChange>(OwningAbility);
	MyObj->TriggerOnce = TriggerOnce;
	//MyObj->AbilitySystemComponent = SystemComponent;
	MyObj->Attribute = Attribute;

	return MyObj;
}

实现Ability Task逻辑

首先重写函数Activate(),负责将下面创建的函数与ASC中的属性改变delegate绑定。实际的方法和我们之前实时更新UI所用的方法是一样的。

virtual void Activate() override;

void OnAttributeChanged(const FOnAttributeChangeData& Data);

OnAttributeChanged的逻辑很简单,通过之前创建的delegate将改变的属性广播出去。如果设置过只触发一次,就调用EndTask()

void UAT_AttributeChange::Activate()
{
	if(IsValid(AbilitySystemComponent) && Attribute.IsValid())
	{
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(this, &UAT_AttributeChange::OnAttributeChanged);
	}
	
	Super::Activate();
}

void UAT_AttributeChange::OnAttributeChanged(const FOnAttributeChangeData& Data)
{
	AttributeChanged.Broadcast(Data.Attribute, Data.NewValue, Data.OldValue);

	if(TriggerOnce)
	{
		EndTask();
	}
}

重写OnDestroy

这部分负责重写销毁Task的方法,首先我们需要将绑定的Uobject全部移除,然后调用父类的Destroy()函数

virtual void OnDestroy(bool bInOwnerFinished) override;

void UAT_AttributeChange::OnDestroy(bool bInOwnerFinished)
{
	if(AbilitySystemComponent)
	{
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).RemoveAll(this);
	}
	Super::OnDestroy(bInOwnerFinished);
}

测试

创建一个Ability用于测试,蓝图如下。

虚幻四Gameplay Ability System入门(12)-Ability Task_第3张图片

然后写个扣血的GE,随便测试一下。可以看到Ability收到并打印出了数据。这个逻辑可以用于伤害技能的一些判断,比如当生命值低于10%后播放一个处决动画等等。

总结

这个GAS入门教程系列写到这里就算是告一个段落了,也算是写一写感想吧。

首先谈谈写这篇文章的原因。我在学习虚幻四的过程中看了很多的教程视频和文章,其中有youtube的视频,有github上的项目和文档,也有很多B站Up主转载的视频。这些视频和文章很多点击量不过千,但却是切切实实地帮助到了我,我没有办法直接回报他们经济利益,但我希望这种资源共享,帮助他人的氛围和行动可以继续下去,让其它同样有需要的人也可以得到帮助,也算是回馈了我受到的帮助。

因此我写这个文章系列并没有什么利益上的追求,唯一希望的就是点击量的增加,和他人评论对我的鼓励,这种成就感可以满足我小小的虚荣心,帮助我将写教程这一吃力的行为继续下去,因为这种没有反馈,看不到回报又很吃力的事情真的是挺难坚持下去的,这让我更佩服那些长期连载教程文章和视频的人了。

很多时候,我必须得承认,中文互联网上这种知识分享的氛围不如英文互联网环境。在写文章的过程中,我是真的没想到我一篇点击只有几十的文章会被别人盗走写成原创,也看到了很多文章被重复无意义的转载让人连溯源都做不到了,甚至我还看到过有人出的中文教程居然就是直接翻译了Udemy上的英文教程,内容几乎没有改变,连素材都没有换就开始售卖。

但同时也有很多人在做着这种吃力不讨好的事情,有人无私的翻译了英文文档,有人在写各种各样不同的教程,我期望自己也能为这样的环境做出一份力。

正值春招,未来我可能去智能汽车的仿真,可能去游戏公司,但我希望可以坚持将写教程这一行为坚持下去,能够帮助到有需要的人,Thanks♪(・ω・)ノ。

GAS入门教程合集

你可能感兴趣的:(虚幻四,虚幻四,Gameplay,Ability,System,游戏开发,unreal,游戏开发,游戏引擎)