翻译自Udemy的视频课程introduction-to-unreal-engine-4-ability-system
01、首先在UE4中启用插件,
02、由于GAS还不完全支持蓝图,需要用到C++ ,首先在工程的cs配置文件中添加 "GameplayAbilities", "GameplayTags", "GameplayTasks",添加后编译一遍工程,有必要的话重新再打开工程,以便VS2019能够识别一些新加载的符号
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "GameplayAbilities", "GameplayTags", "GameplayTasks" });
03、BaseCharacter.cpp文件中添加头文件,并且继承IAbilitySystemInterface接口,在其中添加一个UAbilitySysrtemComponent,另外继承接口就需要重写方法
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "AbilitySystemComponent.h"
#include "BaseCharacter.generated.h"UCLASS()
class ABILITYSYSTEM_API ABaseCharacter : public ACharacter,public IAbilitySystemInterface
{
//IAbilitySystemInterface是用于获取AbilitySystemComponent的接口,
//其内部就只有一个GetAbilitySystemComponent()纯需方法方法
GENERATED_BODY()public:
// Sets default values for this character's properties
ABaseCharacter();protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;public:
// Called every frame
virtual void Tick(float DeltaTime) override;// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
//声明一个UAbilitySystemComponent组件,一样的使用CreateDefaultSubobject的方式来定义
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = BaseCharacter)
UAbilitySystemComponent* AbilitySystemComp;
//重写接口中的虚方法
virtual UAbilitySystemComponent* GetAbilitySystemComponent()const;//该函数用于获取具体的Ability
UFUNCTION(BlueprintCallable, Category = BaseCharacter)
void AquireAbility(TSubclassOfAbilityToAquire);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "public/BaseCharacter.h"// Sets default values
ABaseCharacter::ABaseCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
AbilitySystemComp = CreateDefaultSubobject("AbilitySystemComp");
}// Called when the game starts or when spawned
void ABaseCharacter::BeginPlay()
{
Super::BeginPlay();
}// Called every frame
void ABaseCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);}
// Called to bind functionality to input
void ABaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);}
UAbilitySystemComponent* ABaseCharacter::GetAbilitySystemComponent()const {
return AbilitySystemComp ;
}
void ABaseCharacter::AquireAbility(TSubclassOfAbilityToAquire)
{
//先判断AbilitySytemcomp是否为空
if (AbilitySystemComp) {
//HasAuthrity用于判断客户端是否对Actor有控制权
//再判断传入的技能是否为空
if (HasAuthority() && AbilityToAquire) {
//GiveAbility用于具体的为AbilitySystemComp赋予能力,会调用TryActiveAbility
//FGameplayAbilitySpec是一个用于存储被赋予Ability相关的数据信息的结构体
AbilitySystemComp->GiveAbility(FGameplayAbilitySpec(AbilityToAquire, 1, 0));
//InitAbilityActorInfo用于说明是谁在逻辑上控制Actor(Pawn、Characrter等等)、谁在物理上控制(可以是建筑,也可能和OwnedActor相同)
AbilitySystemComp->InitAbilityActorInfo(this, this);
}
}
}
04、视频中推荐使用番茄插件,其实我并不推荐。一是版权问题(正版并不是太贵),二是性能问题(如果开多线程,每次打开工程CPU会满载),三是安全性(破解的基本都带有病毒),四是目前VS2019对UE4的智能提示已经可以了,里面的符号基本上都能加载出来,没必要再搞个插件。
05、添加.gitignore文件,里面加入一行Content/ParagonShinbi/ 用于忽略掉商城的资源,可以在VS2019中看到目前为止的改动的文件数,
另外上传到仓库的资源大小为787K,
01、创建一个GameplayAbility蓝图,命名为GA_Melee,这个可以在BP_BaseCharacter中用AquireAbility获取,在之前一节中PrimaryAttack会play montage,现在把它放到GA_Melee中,使用playMontageAndWait节点,同样的把Rate调低一点,使得动画播放的不要太快
02、在人物蓝图中,使用AquireAbility(这是在C++中写的函数)获取与使用TryActiveAbilityByClass触发技能,可以看到,把人物蓝图中的play montage放到具体的技能中
03、创建GamePlayEffect蓝图GE_Melee_CoolDown,GamePlayEffect这个是纯信息类,主要是用于说明法术消耗,能量消耗,冷却时间等信息,需要在GA_Melee中的ClassDefaults中的CoolDowns中指定Cost Gameplay EffectClass为GameEffect_Melee
04、另外具体设置GE_Melee_CoolDown里的信息,
05、 添加tag,当一个AbilitySystemComp获得一个技能的时候,他也会相应的增加一个与技能相关的Tag,当该技能冷却完成后,会移除相应的Tag,从而能够再次释放,这里需要填在GrantedTags中,填错Cool Down没效果
06、为了使得CoolDown生效,需要在GA_Melee中 ActiveAbility事件中CommitAbility
当前我们有了Melee attack这个技能,我们想让这个技能发生作用,需要用到attribute set
01、创建C++ AttributeSet 类,命名为BaseAttributeSet,
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "BaseAttributeSet.generated.h"/**
*
*/
UCLASS()
class ABILITYSYSTEM_API UBaseAttributeSet : public UAttributeSet
{
GENERATED_BODY()public:
//创建一个构造函数
UBaseAttributeSet();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AttributeSet)
FGameplayAttributeData Health;
//FGameplayAttributeData拥有BaseValue和AttributeValue两个值
};
#include "BaseAttributeSet.h"
UBaseAttributeSet::UBaseAttributeSet() :Health(200.f)
{//构造函数用于初始化Health
}
02、然后要让我们的BaseCharacter拥有这个Attribute Set,使用前置声明的方式添加如下代码
//声明一个和UBaseAttributeSet组件,一样的使用CreateDefaultSubobject的方式来定义
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = BaseCharacter)
class UBaseAttributeSet* AttributeSetComp;
#include "public/BaseCharacter.h"
#include "BaseAttributeSet.h"
// Sets default values
ABaseCharacter::ABaseCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
AbilitySystemComp = CreateDefaultSubobject("AbilitySystemComp");
AttributeSetComp = CreateDefaultSubobject("AttributeSetComp");
}
03、编译,然后就可以在BP_BaseCharacter中Get和Set这个AttributeSetComp了。如下代码打印测试CurrentHealth值
01、为Mesh中的sword添加capsule碰撞体,调整Socket为Weapon_r,调节大小和位置,效果如下
02、ctrl+W复制BP_BaseCharacter,命名为BP_Enemy,修改Enemy的Mesh,换一个皮肤,以便区分(原视频是先02后01,不妥)
03、为SwordCollision添加Overlap事件,需要判断一下碰撞到的物体是不是敌人,不能是剑,也不能是自身,
首先设置SwordCollision,只和Pawn发生碰撞,
在Ovelap事件中首先判断不能是自身,然后判断是否是敌人,是的话,打印碰撞体的名称
为了屏幕更清晰,断开其他Print String的连接,也可以观察到一次攻击会产生多个碰撞检测。需要在下一节中设置notify来解决
实现的功能就是普通状态,即没有进行攻击动作的时候,Sword是不进行碰撞检测的,只有当攻击动作进行,即动画蒙太奇播放的时候,在Notify处才进行碰撞检测。
01、首先,将SwordCollision设置CollisionEnabled为NoCollision,这样子在普通状态下无碰撞检测
02、创建一个蓝图AnimationNotifyState,命名为ANS_MeleeAttack,首先要在Montage中使用这个NotifyState,放到8-10帧的位置上
03、ANS_MeleeAttack其中的EventGraph重写NotifyBegin和NotifyEnd方法
首先getowner,如果是BP_BaseCharacter的话,获取SwordCollision,然后EnableCollision,设置为query only,不检测physicbody
首先getowner,如果是BP_BaseCharacter的话,获取SwordCollision,然后DisableCollision,
04、现在想在Sword发生碰撞的时候发送GameplayEvent事件(这是GAS自带的事件),其中payload传递技能所作用于的对象
,使用GameplayEventData打包Sword碰撞到的物体作为Payload
05、,然后就可以在GA_Melee中监听gameplayEvent,在playmontage后修改如下
:当Montage播放完成的时候,应该EndAbility,
01、处理伤害需要另外的GameplayEffect,命名为GE_Melee_Damage,指定作用的属性值Health,伤害的大小-20,为Instant立即伤害
02、增添一个新的tag,这次是使用GameplayEffectAssetTag
03、在GA_Melee中实际使用这个GameplayEffect,将之前的printstring 替换如下,作用就是将Damage实际应用到Health上
04、为了实际的查看应用的效果,C++中在BaseAttributeSet重写PostGameplayEffectExecute方法,该方法会在属性值发生修改后执行(UE4.22中这个不能正常工作,没能够正常的打印,Debug时不会执行这一块的代码,也就是说,没能够正常的修改属性值,)问题解决了,之前的BP_Enemy在复制的过程中AttributeSet comp为none,所以对Enmemy使用ApplyDamageToTarget的时候并不会对Health产生影响。应该从BP_BaseCharacter重新派生一个BP_BaseEnemy出来(用派生的方式产生类,不要用复制的方式)
在BaseAttributeSet.h中添加:
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)override;
对应的Cpp文件中添加
#include "BaseAttributeSet.h"
#include"GameplayEffect.h"
#include "GameplayEffectExtension.h"//PostGameplayEffectExecute是在属性值发生修改后自动调用
void UBaseAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{if (Data.EvaluatedData.Attribute.GetUProperty() == FindFieldChecked
(UBaseAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UBaseAttributeSet, Health))) {
//暂时先打印修改后的属性值
UE_LOG(LogTemp, Warning, TEXT("Oh,i got som damagem,now my health is:%f"),Health.GetCurrentValue());
}
}
01、制作HealthBar,锚点居中,添加一个ProgressBar,调整颜色,大小等
02、在Graph中添加一个custom event,命名为SetPercentage,添加一个float型的输入percentage,
03、在BaseAttributeSet中设置一个Delegate,用来传递属性值的修改
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "BaseAttributeSet.generated.h"/**
*
*/
//创建一个Multicast多播类型的委托,里面传入两个参数
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangeDelegate, float, Health, float, MaxHealth);
UCLASS()
class ABILITYSYSTEM_API UBaseAttributeSet : public UAttributeSet
{
GENERATED_BODY()public:
//创建一个构造函数
UBaseAttributeSet();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AttributeSet)
FGameplayAttributeData Health;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AttributeSet)
FGameplayAttributeData MaxHealth;
//FGameplayAttributeData拥有BaseValue和AttributeValue两个值
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)override;
FOnHealthChangeDelegate OnHealthChange;
};
04、在PostGameplayEffectExecute中广播出去
UBaseAttributeSet::UBaseAttributeSet() :Health(200.f),MaxHealth(200.f)
{
//构造函数用于初始化Health
}void UBaseAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{if (Data.EvaluatedData.Attribute.GetUProperty() == FindFieldChecked
(UBaseAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UBaseAttributeSet, Health))) {
//暂时先打印修改后的属性值
UE_LOG(LogTemp, Warning, TEXT("Oh,i got som damagem,now my health is:%f"),Health.GetCurrentValue());
//当属性值发生修改的时候广播出去
OnHealthChange.Broadcast(Health.GetCurrentValue(), MaxHealth.GetCurrentValue());
}
}
05、在BaseCharacter中接收
UFUNCTION()
void OnHealthChange(float Health, float MaxHealth);
UFUNCTION(BlueprintImplementableEvent, Category = BaseCharacter)
void BP_OnHealthChange(float Health, float MaxHealth);//蓝图中实现
// Called when the game starts or when spawned
void ABaseCharacter::BeginPlay()
{
Super::BeginPlay();
//在BeginPlay中动态的绑定委托
AttributeSetComp->OnHealthChange.AddDynamic(this, &ABaseCharacter::OnHealthChange);
}void ABaseCharacter::OnHealthChange(float Health, float MaxHealth)
{
BP_OnHealthChange(Health, MaxHealth);//通知蓝图事件调用
}
06、在BP_BaseEnemy中添加一个widget,命名为HealthBar,并且指定其中的UI为HealthBar
07、在BP_BaseEnemy的EventGraph中,设置HealthBar的数值显示
08、有必要重新关闭Editor,再打开编译一下,运行后效果如下就OK了
01、首先为了人物的生命值不会降低到0以下,需要添加
void UBaseAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{if (Data.EvaluatedData.Attribute.GetUProperty() == FindFieldChecked
(UBaseAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UBaseAttributeSet, Health))) {
//这一段的逻辑放在PreAttributeChange(这个是在属性值发生修改前调用)更好一些
Health.SetCurrentValue(FMath::Clamp(Health.GetCurrentValue(), 0.f, MaxHealth.GetCurrentValue()));
Health.SetBaseValue(FMath::Clamp(Health.GetBaseValue(), 0.f, MaxHealth.GetCurrentValue()));//暂时先打印修改后的属性值
UE_LOG(LogTemp, Warning, TEXT("Oh,i got som damagem,now my health is:%f"),Health.GetCurrentValue());
//当属性值发生修改的时候广播出去
OnHealthChange.Broadcast(Health.GetCurrentValue(), MaxHealth.GetCurrentValue());
}
}
02、当生命值为0时,播放死亡动画,再在BaseCharacter中添加一个蓝图事件函数
UFUNCTION(BlueprintImplementableEvent, Category = BaseCharacter)
void BP_Die();
protected:
bool bIsDeath;
void ABaseCharacter::OnHealthChange(float Health, float MaxHealth)
{
if (Health <= 0.f&& !bIsDeath) {
//生命值小于0,调用蓝图事件来播放死亡动画
bIsDeath = true;
BP_Die();
}
BP_OnHealthChange(Health, MaxHealth);//通知蓝图事件调用
}
03、在BP_BaseEnemy中 ,另外需要添加AM_ShinbiDeath动画蒙太奇,注意指定该蒙太奇的Slot
接下来一节介绍设置简单的敌人AI,让敌人能够发现玩家,并且移动至玩家处,然后使用MeleeAttack技能