在前面的文章中,我们实现了对敌人的属性的初始化,现在敌人也拥有的自己的属性值,技能击中敌人后,也能够实现血量的减少。
现在还需要的就是在技能击中敌人后,需要敌人进行一些击中反馈,比如敌人被技能击中后,可以播放击中的动画,并且显示伤害值。
我们在这一篇文章中,实现敌人被英雄的技能击中时,会播放受击动画,并且在敌人身上添加一个受击标签,角色在受击时,根据标签判断设置移动速度
设置角色受击标签
首先,我们实现在敌人身上添加标签,我们需要通过GE去添加。
创建一个新的GE
命名为GE_HitReact
在里面设置持续时间设置无限时间,我们在技能里面应用它,然后在技能结束是,将其清除
现在,我们还没有受击对应的标签,在c++里添加一个
FGameplayTag Effects_HitReact; //受击 标签
GameplayTags.Effects_HitReact = UGameplayTagsManager::Get()
.AddNativeGameplayTag(
FName("Effects.HitReact"),
FString("受到攻击时,赋予的标签")
);
然后在GE里面使用TargetTagsGameplayEffectComponent组件,它可以将标签应用到目标角色身上
角色监听标签并设置移动速度
我们接下来实现对受击标签的监听,在源码中,受击标签会返回监听的标签和添加标签的个数
我们创建一个委托回调函数
void HitReactTagChanged(const FGameplayTag CallbackTag, int32 NewCount);
并创建变量来表示角色是否处于受击状态
UPROPERTY(BlueprintReadOnly, Category="Combat")
bool bHitReacting = false; //当前是否处于被攻击状态
UPROPERTY(BlueprintReadOnly, Category="Combat")
float BaseWalkSpeed = 250.f; //当前角色的最大移动速度
在AEnemyBase::BeginPlay()中,我们设置对监听函数的回调
AbilitySystemComponent->RegisterGameplayTagEvent(FMyGameplayTags::Get().Effects_HitReact, EGameplayTagEventType::NewOrRemoved)
.AddUObject(this, &ThisClass::HitReactTagChanged);
如果数量大于0,则将其的移动速度设置为0
void AEnemyBase::HitReactTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
bHitReacting = NewCount > 0;
GetCharacterMovement()->MaxWalkSpeed = bHitReacting ? 0.f : BaseWalkSpeed;
}
创建一个受击技能,并应用GE
接下来,我们创建一个新的技能,用来实现受击,是的,受击也是角色的技能。在角色受到攻击时,将触发角色受击技能来实现受击效果。
在技能触发时,我们将受击标签添加到角色身上,这样,之前监听角色标签的回调函数将触发,并设置移动速度,我们并将句柄保持,在技能结束时,方便移除GE
实现设置角色的受击蒙太奇动画
接下来,我们将实现角色受击时播放蒙太奇动画,为了保证通用性,我们将其设置为一个函数,并设置到战斗接口中,这样只需要在战斗接口中获取对应角色的蒙太奇即可。(每个角色的受击动画不一定一样)
首先在战斗接口中实现一个函数,这函数可以在蓝图中被覆写,也可以在蓝图中调用。
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
UAnimMontage* GetHitReactMontage(); //获取受击蒙太奇动画
然后,我们在角色的基类里,对这个函数进行覆写。
virtual UAnimMontage* GetHitReactMontage_Implementation() override;
并增加一个参数用于设置蒙太奇
UPROPERTY(EditAnywhere, Category="Combat")
TObjectPtr<UAnimMontage> HitReactMontage;
在函数实现这里,直接返回蒙太奇
UAnimMontage* ACharacterBase::GetHitReactMontage_Implementation()
{
return HitReactMontage;
}
接着在技能里面,将ASC控制的角色转换为战斗接口,从战斗接口调用函数获取到角色设置的蒙太奇进行播放
接下来就是基于动画创建蒙太奇
并将蒙太奇设置到角色身上,用于函数返回。
激活受击技能
接下来,就是我们实现对技能的激活,和上一篇一样,我们创建了一个敌人的DataAsset,我们希望可以将敌人所拥有的初始技能都设置在数据里,并在初始化时使用。
在CharacterClassInfo.h里增加一个参数,用于设置创建敌人时所拥有的初始技能
UPROPERTY(EditDefaultsOnly, Category="Common Class Defaults")
TArray<TSubclassOf<UGameplayAbility>> CommonAbilities;
之前英雄角色初始化技能我们创建了一个应用技能的函数
它是创建技能实例,并给与ASC,我们将给敌人创建一个通用的函数,并放到函数库中
在函数技能库里新增一个函数用于初始化角色技能
//初始化角色的技能
UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|CharacterClassDefaults")
static void GiveStartupAbilities(const UObject* WorldContextObject, UAbilitySystemComponent* ASC);
函数实现就是获取到数据资产里面的技能列表,创建实例并给与角色
void UMyAbilitySystemBlueprintLibrary::GiveStartupAbilities(const UObject* WorldContextObject, UAbilitySystemComponent* ASC)
{
//获取到当前关卡的GameMode实例
const AMyGameModeBase* GameMode = Cast<AMyGameModeBase>(UGameplayStatics::GetGameMode(WorldContextObject));
if(GameMode == nullptr) return;
const AActor* AvatarActor = ASC->GetAvatarActor();
//从实例获取到关卡角色的配置
UCharacterClassInfo* CharacterClassInfo = GameMode->CharacterClassInfo;
//遍历角色拥有的技能数组
for(const TSubclassOf<UGameplayAbility> AbilityClass : CharacterClassInfo->CommonAbilities)
{
FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(AbilityClass, 1); //创建技能实例
ASC->GiveAbility(AbilitySpec); //只应用不激活
}
}
在敌人基类里,开始事件时,我们调用函数库的初始化技能函数,将技能应用到角色身上
void AEnemyBase::BeginPlay()
{
Super::BeginPlay();
//设置角色的初始移动速度
GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed;
//初始化角色的ASC
InitAbilityActorInfo();
//初始化角色的技能
UMyAbilitySystemBlueprintLibrary::GiveStartupAbilities(this, AbilitySystemComponent);
...
方便测试,在PostGameplayEffectExecute函数里,之前设置血量下面有判断,我们在角色没有被击杀时,让其触发受击技能。激活我们是使用的根据标签激活,它需要传入一个FGameplayTagContainer 类型的参数。
if(Data.EvaluatedData.Attribute == GetIncomingDamageAttribute())
{
const float LocalIncomingDamage = GetIncomingDamage();
SetIncomingDamage(0.f);
if(LocalIncomingDamage > 0.f)
{
const float NewHealth = GetHealth() - LocalIncomingDamage;
SetHealth(FMath::Clamp(NewHealth, 0.f, GetMaxHealth()));
const bool bFatal = NewHealth <= 0.f; //血量小于等于0时,角色将会死亡
if(!bFatal)
{
FGameplayTagContainer TagContainer;
TagContainer.AddTag(FMyGameplayTags::Get().Effects_HitReact);
Props.TargetASC->TryActivateAbilitiesByTag(TagContainer); //根据tag标签激活技能
}
}
}
接下来,编译代码,打开UE,将我们之前创建的受击技能设置到数据资产中
我们将使用标签激活技能,所以,在技能里,还需要将受击标签设置给技能
我们不需要在每次激活时创建一个新的实例,只需要一个角色生成一个实例重复利用即可。
由于它一个角色对应一个单例,每次触发都是相同的实例,所以,我们需要在其播放完成后,将其结束,才可以触发,所以,在敌人的受击动画播放完成后,我们需要将敌人身上的受击标签清楚(如果GE添加的,只需要将对应的GE清除,标签也会随着清除)并结束技能。
接下来,就是测试效果,我们攻击敌人,看看其是否能够播放动画。