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

108 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 角色类
逻辑位于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蓝图](http://blueprintue.cn/render/-9q3fr9h)
## 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://github.com/tranek/GASDocumentation)配合最近挺火的https://www.deepl.com/translator翻译效果更佳。
除了文档之外它还包含了一个小项目我大致看了一下推荐在学习过ActionRPG后在学习这个项目。
这个项目使用AbilityInput绑定与GameplayCue使用Tag进行技能施放与阻止施放判断同时有更多的c++代码对于ActionRPG是一个很好的补充。
另外我还找到一个中文版(然而并不全),大致看了一下应该是学习了这个项目后写的笔记,英语不太好的可以参考下:
https://tianqi.li/2020/02/29/gas-document.html