今天就是本教程的最后一章了,前一篇当中提到,Ability的运行如果没有Task的话,实际上是在一帧内运行完成的,这肯定是不能满足实际情况的,因此我们需要Ability Task,让Ability可以在更长的时间范围内相应处理不同的动作。
我们之前持续使用的PlayMontageAndWait,WaitTargetData,Wait,WaitGameplayEvent等等功能全部都是Ability Task。既然如此,我们也会想要实现自己的技能任务,本篇教程就介绍一下实现一个简单的Ability Task的方法。
首先创建C++类,我们可以看到AbilityTask实际上是继承于GameplayTask,GameplayTask继承于虚幻四的万物之父Uobject类。
这里我打算实现一个功能,可以等待某一属性发生改变。所以命名为AT_AttributeChange。
打开生成的类,先不着急实现代码,我们先进去它的基类AbilityTask中。在这里EPIC官方写的文档已经大致说明了实现一个技能任务的要求,我这里大致讲解一下:
/**
* 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!
*/
大致理解了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用于测试,蓝图如下。
然后写个扣血的GE,随便测试一下。可以看到Ability收到并打印出了数据。这个逻辑可以用于伤害技能的一些判断,比如当生命值低于10%后播放一个处决动画等等。
这个GAS入门教程系列写到这里就算是告一个段落了,也算是写一写感想吧。
首先谈谈写这篇文章的原因。我在学习虚幻四的过程中看了很多的教程视频和文章,其中有youtube的视频,有github上的项目和文档,也有很多B站Up主转载的视频。这些视频和文章很多点击量不过千,但却是切切实实地帮助到了我,我没有办法直接回报他们经济利益,但我希望这种资源共享,帮助他人的氛围和行动可以继续下去,让其它同样有需要的人也可以得到帮助,也算是回馈了我受到的帮助。
因此我写这个文章系列并没有什么利益上的追求,唯一希望的就是点击量的增加,和他人评论对我的鼓励,这种成就感可以满足我小小的虚荣心,帮助我将写教程这一吃力的行为继续下去,因为这种没有反馈,看不到回报又很吃力的事情真的是挺难坚持下去的,这让我更佩服那些长期连载教程文章和视频的人了。
很多时候,我必须得承认,中文互联网上这种知识分享的氛围不如英文互联网环境。在写文章的过程中,我是真的没想到我一篇点击只有几十的文章会被别人盗走写成原创,也看到了很多文章被重复无意义的转载让人连溯源都做不到了,甚至我还看到过有人出的中文教程居然就是直接翻译了Udemy上的英文教程,内容几乎没有改变,连素材都没有换就开始售卖。
但同时也有很多人在做着这种吃力不讨好的事情,有人无私的翻译了英文文档,有人在写各种各样不同的教程,我期望自己也能为这样的环境做出一份力。
正值春招,未来我可能去智能汽车的仿真,可能去游戏公司,但我希望可以坚持将写教程这一行为坚持下去,能够帮助到有需要的人,Thanks♪(・ω・)ノ。
GAS入门教程合集