在前面的文章中,我们实现了对敌人的属性的初始化,现在敌人也拥有的自己的属性值,技能击中敌人后,也能够实现血量的减少。
 现在还需要的就是在技能击中敌人后,需要敌人进行一些击中反馈,比如敌人被技能击中后,可以播放击中的动画,并且显示伤害值。
 我们在这一篇文章中,实现敌人被英雄的技能击中时,会播放受击动画,并且在敌人身上添加一个受击标签,角色在受击时,根据标签判断设置移动速度
设置角色受击标签
首先,我们实现在敌人身上添加标签,我们需要通过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清除,标签也会随着清除)并结束技能。
 
 接下来,就是测试效果,我们攻击敌人,看看其是否能够播放动画。
 



















