BlueRoseNote/05-SDHGame/ActionRPG时代的设计/ActionRPG中的攻击逻辑与其他实现方法.md
2023-06-29 11:55:02 +08:00

8.2 KiB
Raw Permalink Blame History

角色类

逻辑位于BP_PlayerCharacter中通过按键事件执行ActivateAbilitiesWithTags定义于ARPGCharacterBase本质上是执行AbilitySystemComponent的TryActivateAbilitiesByTag函数、ActivateAbilitiesWithItemSlot定义于ARPGCharacterBase本质上通过查询ItemSlot来激活对应的Ability使用了AbilitySystemComponent的TryActivateAbility函数来施放“Ability”。

Ability类

在ActionRPG中攻击Ability以PlayMontageAndWaitForEvent为主这个节点是一个GameplayTask具体的介绍可以参考我之前的文章。攻击Ability的基类为Abilities/Shared/GA_MeleeBase。

一种是武器攻击判定在Ability类中通过PlayMontageAndWaitForEvent节点播放Montage之后通过在Montage中设置的AnimNotifyWeaponAttackNS调用对应角色的WeaponActor的EventBeginWeaponAttack来开启WeaponActor的碰撞判定。之后将碰撞的Actor信息通过SendGameplayEventToActor发送给角色。

另一种是范围技能直接通过AnimNotifyRangeAttackNS向对应角色发送EventTag来触发SendGameplayEventToActor

PlayMontageAndWaitForEvent事件处理

PlayMontageAndWaitForEvent之后会接收到信息本质是角色的AbilitySystemComponent接收到事件之后再广播委托Event会包含发起者与目标对象之后调用ApplyEffectContainer函数将Ability类中设置的GameplayEffect应用到与之对应的target上通过设置对应的URPGTargetType派生类

URPGTargetType

在GA_MeleeBase中默认使用的目标对象获取类为URPGTargetType_UseEventData。可以看得出是直接使用了EventData。

void URPGTargetType_UseEventData::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
	const FHitResult* FoundHitResult = EventData.ContextHandle.GetHitResult();
	if (FoundHitResult)
	{
		OutHitResults.Add(*FoundHitResult);
	}
	else if (EventData.Target)
	{
		OutActors.Add(const_cast<AActor*>(EventData.Target));
	}
}

除了c++ 实现的URPGTargetTypeActionRPG还使用蓝图实现了TargetType_SphereTrace其他皆只为参数修改。本质上还是碰撞判断所以个人还是推荐使用C++来实现,以获得较好的性能。

WeaponActor

其中攻击判断逻辑位于WeaponActor中的ActorBeginOverlap事件其他逻辑主要为开启与关闭碰撞判定。

Instigator为Actor中伤害系统Damage中的组成部分可以理解为伤害的发起对象。在WeaponActor中即为装备了该武器的角色类。

攻击判定逻辑大致为:

  1. 判断碰撞到武器是否为武器本身。
  2. 如果不是且处于攻击过程中就向WeaponActor发送GameplayEvent通过

WeaponActor蓝图

ActionRPG中的Combo实现

官方的ActionRPG是将连击逻辑中跳转控制放在Character类中。

在播放Montage的过程中触发AnimNotifyAnimNotify会设置BP_PlayerCharacter类中的JumpSectionNotify引用还会设置一个Bool变量。并在需要跳转MontageSection时读取SectionName以进行跳转。

DoMeleeAttack中的逻辑为如果符合若干前置条件并且没有施放GA_MeleeBase及其子类能力就施放指定Melee能力。如果正在施放Melee能力则进行Montage跳转调用AnimInstance的MontageSetNextSection

具体的实现请参考ActionRPG案例中的Blueprints/AnimNotifies/JumpSectionNS与Blueprints/BP_PlayerCharacter中的DoMeleeAttack函数。

本人尝试的方法

本人想把所有逻辑都写进Ability中尝试了以下几个思路

通过AbilityActorInfo获取AnimInstance之后绑定AnimationInstance中的OnPlayMontageNotifyBegin与End委托来获取Notify信息。

这个方法因为OnPlayMontageNotifyBegin与End委托没有暴露给蓝图并且还需要所以需要编写c++有点麻烦。另外我还写了个AnimNotifyState类重写若干虚函数以绑定这两种委托。因为默认只有UAnimNotify_PlayMontageNotifyWindow与UAnimNotify_PlayMontageNotify会触发这两种委托。这里先介绍一下

ActorInfo 是一个包含各个组件指针的类换句话说就是一个GAS组件拥有者的相关组件指针类应该是在Component初始化时填充数据

/** Cached off data about the owning actor that abilities will need to frequently access (movement component, mesh component, anim instance, etc) */
TSharedPtr<FGameplayAbilityActorInfo>	AbilityActorInfo;

你可以在Ability蓝图类中调用GetActorInfo获取,之后再通过ActorInfo获取AnimInstance操作Montage。

UAnimInstance* AnimInstance = AbilityActorInfo.IsValid() ? AbilityActorInfo->GetAnimInstance() : nullptr;

当然也可以使用

/** Returns the avatar actor for this component */
AActor* GetAvatarActor() const;

进行获取。

但上面介绍的方法依然需要将Montage跳转逻辑写在角色类中这主要是因为调用TryActivateAbilityByClass函数会重复创建Ability实例。所以

我写了个新的TryActivateAbilityByClassOrReturnInstance函数当存在指定类型的Ability实例就不会创建新的实例而是返回这个实例。

这样我们就可以把所有逻辑都写在Ability类中了。但是这个方法需要编写若干c++代码比如增加GamplayAbility存在实例时的事件、增加若干TryActivateAbility系列函数这对只会蓝图的开发开发者不太友好。所以下面我将介绍另一种方法

使用GameplayEvent控制

这个方法的好处在于不用写c++代码能把技能相关代码都写进Ability类中。在外部使用Event对Ability进行控制易于观察与控制同时也进行了解耦。

  1. 在AnimNotify中使用GetOwner获取Character后作为SendEventToActor的参数使用并在SendEventToActor的参数中填入对应的GameplayEvent标签与所需的Payload结构体。
  2. 在角色类中对应的输入事件中首先判断是否存在对应标签的Ability参考Blueprints/BP_Character中的IsUsingMelee之后根据结果执行TryActivateAbilityByClass构建Ability或者调用SendGameplayEventToActor发送Combo事件。
  3. 在对应Ability中RPGAbilityTask_PlayMontageAndWaitForEvent节点的RecivedEvent下判断接收到EventTag并执行对应的逻辑比如接收到ComboEvent则进行Montage跳转接收到HitEvent则将DamageEffect应用到目标上。

如果你有强迫症想把所有逻辑都写进Ability中那就把这个方法与上一种方法合起来使用吧。

有关SendGameplayEventToActor

1.SendEventToActor所用的GameplayEventTag必须是Event的子标签。 其实是因为ActionRPG的PlayMontageAndWaitForEvent节点加了过滤事件EventTag形参

2.SendEventToActor的Payload形参中的OptionalObject可以用来传递自定义的UObject以此来达到传递自定义数据的目的。具体操作为使用ConstructObjectFromClass节点对自己定义的UObject在UObject中定义各种数据变量进行实例化C++好像是NewObject再对实例进行修改后修改变量或者调用函数操作连入OptionalObject。之后就可以在RecivedEvent委托中获取这个UObject。 3.SendEventToActor的目标Actor必须继承IAbilitySystemInterface接口。

结语

以上就是ActionRPG与本人Demo的攻击判断逻辑但有一些还是需要改进例如对于使用骨骼模型的PhysicalAsset进行攻击判断的方案这套逻辑容易造成重复判定。

在看源码的时候发现了个不错的GameplayAbility文档 https://github.com/tranek/GASDocumentation配合最近挺火的https://www.deepl.com/translator翻译效果更佳

除了文档之外它还包含了一个小项目我大致看了一下推荐在学习过ActionRPG后在学习这个项目。 这个项目使用AbilityInput绑定与GameplayCue使用Tag进行技能施放与阻止施放判断同时有更多的c++代码对于ActionRPG是一个很好的补充。

另外我还找到一个中文版(然而并不全),大致看了一下应该是学习了这个项目后写的笔记,英语不太好的可以参考下: https://tianqi.li/2020/02/29/gas-document.html