在前面,我们实现了敌人的AI系统,敌人可以根据自身的职业进行匹配对应的攻击方式。比如近战战士会靠近目标后进行攻击然后躲避目标的攻击接着进行攻击。我们实现了敌人的AI行为,但是现在还没有实现需要释放的技能,接下来,我们将实现敌人的使用的技能,刚好回顾一下之前的实现的功能。
首先基于之前的伤害技能基类,创建一个近战技能基类
命名为近战技能 RPGMeleeAbility
我们要实现技能的激活,需要使用到GameplayTag,所以我们增加一个新的技能标签用于激活。
在RPGGameplayTags.h增加新的标签
FGameplayTag Abilities_Attack; //攻击技能激活标签
然后将标签添加到标签管理器
/*
* Abilities
*/
GameplayTags.Abilities_Attack = UGameplayTagsManager::Get()
.AddNativeGameplayTag(
FName("Abilities.Attack"),
FString("攻击技能标签")
);
接下来,我们要实现在角色数据表里面配置它的技能,打开Character Class Info.h增加对应的属性设置
//对应每个职业的属性和技能
USTRUCT()
struct FCharacterClassDefaultInfo
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, Category="Class Defaults")
TSubclassOf<UGameplayEffect> PrimaryAttributes;
UPROPERTY(EditDefaultsOnly, Category="Class Defaults")
TArray<TSubclassOf<UGameplayAbility>> StartupAbilities;
};
后面在UE的蓝图里面设置以后,我们可以在结构体内获取需要应用的技能。接下来,我们还需要回忆一下如何应用敌人的技能的,我们是在敌人的基类里面,去实现的技能的应用,
我们要扩展这个函数,增加传入角色的类型,通过类型去获取特定职业的技能。
//初始化角色的技能
UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|CharacterClassDefaults")
static void GiveStartupAbilities(const UObject* WorldContextObject, UAbilitySystemComponent* ASC, ECharacterClass CharacterClass);
扩展函数,从数据结构中获取到数据并应用到角色的ASC上
void URPGAbilitySystemBlueprintLibrary::GiveStartupAbilities(const UObject* WorldContextObject, UAbilitySystemComponent* ASC, ECharacterClass CharacterClass)
{
//从实例获取到关卡角色的配置
UCharacterClassInfo* CharacterClassInfo = GetCharacterClassInfo(WorldContextObject);
if(CharacterClassInfo == nullptr) return;
//从战斗接口获取到角色的等级
ICombatInterface* CombatInterface = Cast<ICombatInterface>(ASC->GetAvatarActor());
int32 CharacterLevel = 1;
if(CombatInterface)
{
CharacterLevel = CombatInterface->GetPlayerLevel();
}
//应用角色拥有的技能数组
for(const TSubclassOf<UGameplayAbility> AbilityClass : CharacterClassInfo->CommonAbilities)
{
FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(AbilityClass, CharacterLevel); //创建技能实例
ASC->GiveAbility(AbilitySpec); //只应用不激活
}
//获取到默认的基础角色数据
const FCharacterClassDefaultInfo ClassDefaultInfo = CharacterClassInfo->GetClassDefaultInfo(CharacterClass);
//应用职业技能数组
for(const TSubclassOf<UGameplayAbility> AbilityClass : ClassDefaultInfo.StartupAbilities)
{
FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(AbilityClass, CharacterLevel); //创建技能实例
ASC->GiveAbility(AbilitySpec); //只应用不激活
}
}
敌人的角色类型也是设置在敌人基类上的,所以我们可以直接调用时传入
//初始化角色的技能
if(HasAuthority()){
URPGAbilitySystemBlueprintLibrary::GiveStartupAbilities(this, AbilitySystemComponent, CharacterClass);
}
在UE中设置
接着我们可以编译代码打开UE,在UE里面创建一个对应的蓝图类
命名为GA_GoblinSpearAttack
首先设置技能标签,我们就可以通过标签激活技能。
在技能里实现测试代码,在触发技能时,绘制一个绿色的球
接着打开我们的数据表,现在每个职业多了一个需要设置的技能数组,将我们创建的技能添加给战士
攻击是由AI触发的,所以,我们在AI的攻击任务中,通过标签激活技能即可。我们从控制的pawn中获取到ASC,然后通过标签激活技能
创建的变量我们可以将其设置外部可以设置
然后再行为树中设置激活的技能标签
接着运行测试效果,如果允许哥布林在攻击时绘制绿色的球,证明我们技能激活成功。
设置技能攻击蒙太奇
回忆一下之前我们实现的火球术,需要使用一个蒙太奇动画,然后在蒙太奇动画中触发事件发射火球实现技能的释放。接下来,我们使用同样的思路实现近战攻击的技能。
找到敌人的攻击动画,创建一个蒙太奇
创建名称AM_Attack_GoblinSpear,方便在蒙太奇下拉中选择
在蒙太奇中,我们需要增加MotionWarping来控制朝向,注意,动画必须要支持根动画
设置角色在蓄力的时候转向
设置触发名称,关闭移动,只留下转向,切换转向类型为朝向目标
我们有了蒙太奇,还需要实现在攻击时朝向攻击目标,按照之前的实现方式,我们通过MotionWarpping插件实现转向。
添加转向插件
我们需要在敌人基类身上增加MotionWarpping插件
实现调用,和主角的一样即可,其实这里我们可以实现一个角色基类蓝图,在里面设置一些通用的函数,这里先不改。
要实现这个功能,我们需要让敌人知道攻击目标的位置,然后攻击时朝向它,我们需要在AI控制器中获取到目标。
我们在敌人接口里增加两个函数,用于设置和获取攻击目标,这里设置了BlueprintNativeEvent,它默认给我们实现,并且可以在蓝图中实现此函数,如果在蓝图中实现了此函数,在蓝图中调用,将使用蓝图的版本。
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void SetCombatTarget(AActor* InCombatTarget); //设置敌人的攻击目标
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
AActor* GetCombatTarget() const; //获取敌人的攻击目标
接下来我们在敌人基类里面覆写这两个函数,并去实现,这样写法是因为我们是通过UE让蓝图去实现的此函数,所以我们需要加上Vitrual和后面的_Implementation
/* IEnemyInterface敌人接口 */
virtual void HighlightActor() override; //高亮
virtual void UnHighlightActor() override; //取消高亮
virtual AActor* GetCombatTarget_Implementation() const override;
virtual void SetCombatTarget_Implementation(AActor* InCombatTarget) override;
/* IEnemyInterface敌人接口 结束 */
在cpp中实现这两个函数,我们也可以在蓝图中实现,这里看个人喜好
AActor* ARPGEnemy::GetCombatTarget_Implementation() const
{
return CombatTarget;
}
void ARPGEnemy::SetCombatTarget_Implementation(AActor* InCombatTarget)
{
CombatTarget = InCombatTarget;
}
有了设置攻击目标的函数,我们在AI攻击任务里面就可以设置攻击对象了。
在任务里增加一个黑板键选择器,用于在AI行为树中设置绑定的黑板键
在AI行为树中设置攻击目标为跟随目标黑板键
这样我们就可以在任务里面获取攻击目标,并设置给控制的Pawn身上
接下来,我们在技能里面,从角色身上获取到攻击目标,然后转换为战斗接口,调用偏转朝向的函数,将朝向位置设置,然后再调用蒙太奇播放动画
接着允许查看效果,是否真的能捅向目标
添加攻击事件
接下来,我们需要在蒙太奇动画里,攻击的那一刻,触发一个事件,来告诉技能当前可以计算攻击。就像之前实现火球术时的一样。
由于我们没有设置对应的通知,我们去标签管理器增加一个近战攻击的通知标签。由于这个标签不需要在c++内使用,所以我们可以直接通过UE添加。
设置属于攻击的近战攻击标签
设置完成,打开蒙太奇,设置到通知上面,这样就可以在技能里面去接收对应的标签通知了。
然后我们在技能中设置等待游戏事件即可,Only Match Exact是标签必须全部匹配才可以触发事件
现在我们接收到事件了,还需要考虑如何触发攻击。在之前实现火球从法杖杖尖处释放时,我们在战斗接口设置了一个获取技能释放位置的函数,所以我们可以通过获取到设置的位置,生成一个碰撞球体,判断是否和目标产生重叠实现攻击并应用GE。
但是当前函数无法在蓝图中调用,所以,我们需要修改它,将其变成可以在蓝图中调用的版本
//获取技能释放位置,通过在蓝图中设置获取WeaponTipSocketName的位置
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
FVector GetCombatSocketLocation() const;
并将cpp的实现删除。
在角色基类修改它的继承,修改成GetCombatSocketLocation_Implementation
virtual FVector GetCombatSocketLocation_Implementation() const override;
并将实现代码函数名称也修改掉
FVector ARPGCharacter::GetCombatSocketLocation_Implementation() const
{
return Weapon->GetSocketLocation(WeaponTipSocketName);
}
完成以后,就可以编译打开UE,并设置它的WeaponTipSocketName
这里名称,我们可以在武器骨骼上面增加插槽实现
接着在技能蓝图里,我们首先实现接收到事件后绘制一个调试球体,来表现攻击范围
运行查看释放能够正确绘制出调试球体
我们在点击实现角色攻击时,发现一个问题,有个地方报错了,提示我们不要在接口中直接调用Event函数。而是调用Execute_GetCombatSocketLocation。
往前定位发现是在调用,直接调用获取位置,由于前面我们将其修改成了蓝图编译生成函数,所以在C++里面不能够直接调用
所以我们修改此行代码为使用Execute_GetCombatSocketLocation调用
const FVector SocketLocation = ICombatInterface::Execute_GetCombatSocketLocation(GetAvatarActorFromActorInfo());
接着运行测试就行了。