在前面的文章,我们实现了使用GameplayTag和InputAction的对应绑定的数据,并且添加到了增强输入映射的上下文中,实现了通过按键打印对应的GameplayTag,这只是我们基础需要制作的。目的主要是为了实现在GameplayAblity上面设置对应的Tag,在按下对应按键时,可以激活技能。
前面的步骤实现了,接下来,我们将在GA的基类上面增加一个GameplayTag配置属性,然后在给角色应用GA时,将GameplayTag设置给GA的实例,在触发InputAction的时候,通过遍历,找到被应用的GA里面有相同的Tag的GA,然后通过代码去触发。
实现
首先,我们需要再技能的基类上增加一个GameplayTag的配置属性,这样,我们就可以在技能蓝图上设置它所使用的GameplayTag了。
public:
UPROPERTY(EditDefaultsOnly, Category="Input")
FGameplayTag StartupInputTag;
在ASC中实现新的激活事件
接下来,我们要实现在运行时,应用GA时,需要在GamplayAbilitySpec身上将设置的GameplayTag存储下来。
DynamicAbilityTags是一个FGameplayTagContainer类型,它也是可以复制到服务器的,并且在创建GameplayAbilitySpec自动生成。
AbilitySpec.Ability是可以从Spec身上去获取实例的技能基类,并且它也是一个常量对象,我们在其身上设置的参数需要再常量身上去获取。
AbilitySpec.DynamicAbilityTags.AddTag(Cast<UGameplayAbilityBase>(AbilitySpec.Ability)->StartupInputTag);
我们修改ASC中添加技能的函数,它接收的参数是一个技能类的列表,然后遍历,我们在遍历里面通过类去创建Spec实例,如果Spec实例能够转换成我们创建的技能基类对象,那么,我们将基类对象身上设置的Tag设置到DynamicAbilityTags里面。
最后,我们将不会直接激活技能,而是只应用技能,然后通过按键触发技能。
void UAbilitySystemComponentBase::AddCharacterAbilities(const TArray<TSubclassOf<UGameplayAbility>>& StartupAbilities)
{
for(const TSubclassOf<UGameplayAbility> AbilityClass : StartupAbilities)
{
FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(AbilityClass, 1);
if(const UGameplayAbilityBase* AbilityBase = Cast<UGameplayAbilityBase>(AbilitySpec.Ability))
{
AbilitySpec.DynamicAbilityTags.AddTag(AbilityBase->StartupInputTag);
GiveAbility(AbilitySpec); //只应用不激活
// GiveAbilityAndActivateOnce(AbilitySpec); //应用技能并激活一次
}
}
}
下面,我们将在ASC上面增加两个函数,一个是触发技能的悬停时触发的函数,另一个是触发技能时按键离开时的。这两个函数都需要一个参数Tag去查找需要激活技能。
void AbilityInputTagHold(const FGameplayTag& InputTag);
void AbilityInputTagReleased(const FGameplayTag& InputTag);
在函数实现中,我们首先判断传入的InputTag是否可用
if(!InputTag.IsValid()) return;
然后遍历所有已经应用的技能,GetActivatableAbilities()将返回一个可激活技能的列表
for(auto AbilitySpec : GetActivatableAbilities())
然后我们将对Tag进行比对,判断Spec上设置的Tag和传入的Tag是否相同,如果相同,将对技能处理。
if(AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
在技能悬停事件里面,我们首先调用告知GameplayAbility,此技能被触发按下事件,
AbilitySpecInputPressed(AbilitySpec);
防止技能重复被激活,判断当前技能是否处于激活状态
if(!AbilitySpec.IsActive())
尝试激活技能,这里尝试激活,是因为技能激活有很多限制,你调用了激活,只有在符合条件的情况下,才真正被激活。
TryActivateAbility(AbilitySpec.Handle);
而在Released函数内,我们调用了下面函数,告知技能按键事件被抬起。
AbilitySpecInputReleased(AbilitySpec);
这两个事件会触发技能上面的回调,来实现更多的逻辑,我们可以在技能里面覆盖它来实现
virtual void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
virtual void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
下面是在ASC实现的一个激活事件和取消事件的完整代码。
void UAbilitySystemComponentBase::AbilityInputTagHold(const FGameplayTag& InputTag)
{
if(!InputTag.IsValid()) return;
for(auto AbilitySpec : GetActivatableAbilities())
{
if(AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
{
AbilitySpecInputPressed(AbilitySpec);
if(!AbilitySpec.IsActive())
{
TryActivateAbility(AbilitySpec.Handle);
}
}
}
}
void UAbilitySystemComponentBase::AbilityInputTagReleased(const FGameplayTag& InputTag)
{
if(!InputTag.IsValid()) return;
for(auto AbilitySpec : GetActivatableAbilities())
{
if(AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
{
AbilitySpecInputReleased(AbilitySpec);
}
}
}
在PlayerController中修改并调用ASC的激活事件
接下来,我们将在PlayerController(PC主要储存角色操作的的相关内容的)里,触发操作后,调用ASC身上新创建的函数来触发技能激活。现在,我们还无法在PC身上获取ASC,所以,首先添加一个获取ASC的函数。
我们先创建一个变量存储ASC,然后再增加一个获取ASC的函数。
UPROPERTY()
TObjectPtr<UAbilitySystemComponentBase> AbilitySystemComponentBase;
UAbilitySystemComponentBase* GetASC();
然后在函数实现这里,判断ASC是否被设置,如果没有被设置,将通过GAS的函数库去获取,这个静态函数将通过接口去获取
UAbilitySystemComponentBase* APlayerControllerBase::GetASC()
{
if(AbilitySystemComponentBase == nullptr)
{
AbilitySystemComponentBase = Cast<UAbilitySystemComponentBase>(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetPawn()));
}
return AbilitySystemComponentBase;
}
接着,我们修改之前在PC里面触发的事件的函数,之前实现到了打印Tag,现在我们将其修改掉, 不再打印
void APlayerControllerBase::AbilityInputTagPressed(FGameplayTag InputTag)
{
GEngine->AddOnScreenDebugMessage(1, 3.f, FColor::Red, *InputTag.ToString());
}
void APlayerControllerBase::AbilityInputTagReleased(FGameplayTag InputTag)
{
GEngine->AddOnScreenDebugMessage(2, 3.f, FColor::Blue, *InputTag.ToString());
}
void APlayerControllerBase::AbilityInputTagHold(FGameplayTag InputTag)
{
GEngine->AddOnScreenDebugMessage(3, 3.f, FColor::Yellow, *InputTag.ToString());
}
在函数里,我们需要通过ASC去调用前面实现的激活技能函数,但是我们还无法确保ASC不为空指针,先做一下判断,然后返回。
void APlayerControllerBase::AbilityInputTagPressed(FGameplayTag InputTag)
{
// GEngine->AddOnScreenDebugMessage(1, 3.f, FColor::Red, *InputTag.ToString());
}
void APlayerControllerBase::AbilityInputTagReleased(FGameplayTag InputTag)
{
if(GetASC() == nullptr) return;
GetASC()->AbilityInputTagReleased(InputTag);
}
void APlayerControllerBase::AbilityInputTagHold(FGameplayTag InputTag)
{
if(GetASC() == nullptr) return;
GetASC()->AbilityInputTagHold(InputTag);
}
测试
接下来,我们编译代码,解决bug,然后打开UE,找到之前我们做技能测试的蓝图,上面已经可以设置Tag了。
我们设置一个使用鼠标左键触发当前技能
我们之前测试时,在技能激活时触发ActivateAbility字符串打印,然后在一秒后触发技能关闭,并在屏幕打印技能结束
编辑运行,正常运行技能将不会被激活,而是在鼠标左键点击时触发