本文介绍一个基于GAS的伤害计算的扩展,是一套对伤害计算中各个属性计算进行调整和修饰的方案。
一般的伤害计算流程为,获取包括攻击力,防御力,伤害百分比等伤害属性,然后计算出伤害。
尽管伤害属性可以做到大部分的事情,但是仍然有一些更复杂的情况,很难全部都通过属性的方式去实现。比如对眩晕的敌人造成额外的伤害,不太可能单独作为一个属性存在。否则还会有对击飞的敌人,对防御提升的敌人,对无法移动的敌人造成更多的伤害。每一种新的效果都需要增加新的属性,使用起来也过于繁琐和麻烦。
而如果把这些可能的伤害条件放到伤害计算类中,通过一个个if去判断,有感觉有些不太合理,也不方便进行管理。
因此我考虑在GE的基础上扩展一些功能,用于一些更加灵活和琐碎的伤害情况。
这里还是贴一下写的文章,有兴趣的可以关注一下
回合制游戏战斗框架jfpejv68no.feishu.cn/docx/YFATdbdQfouMtAxYqCCcacVPnkd
伤害的动态修饰实现
新增类GameplayEffectAdjustment ,主要有两个函数组成。CanApplyEffectAdjustment 用于判断属性调整是否可以生效。如果生效的话,TryApplyEffectAdjustment 函数将需要调整的数值写入AdjustmentData中
scss
UCLASS(DefaultToInstanced, EditInlineNew, Blueprintable, BlueprintType)
class LYRAGAME_API UGameplayEffectAdjustment : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent)
bool CanApplyEffectAdjustment(const FGameplayEffectSpecHandle& EffectSpecHandle, UAbilitySystemComponent* SourceASC,
UAbilitySystemComponent* TargetASC);
UFUNCTION(BlueprintNativeEvent)
void TryApplyEffectAdjustment(TMap<FGameplayTag, float>& AdjustmentData);
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Adjustment)
TArray<FGameplayEffectAdjustmentData> Adjustment;
};
然后对GameplayEffect的代码进行一定的修改,新增两个数组,分别储存在施放伤害GE时,自己作为source和target时的伤害修饰。
举个例子,如果有一个效果,对眩晕的敌人伤害增加50%,这个效果就会放SourceGrantEffectAdjustments 。而对于效果,眩晕时承受伤害减少50%,那么这个效果就存储在TargetGrantEffectAdjustments中
ini
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Instanced, Category=EffectAdjustment)
TArray<UGameplayEffectAdjustment*> SourceGrantEffectAdjustments;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Instanced, Category=EffectAdjustment)
TArray<UGameplayEffectAdjustment*> TargetGrantEffectAdjustments;
修改完GE后,我们需要让GE生效时,角色拥有GE的伤害修饰效果,在GE失效后,移除对应的伤害修饰效果。
创建一个存储伤害修饰器object的结构体。使用结构体而不是直接用object,我是考虑到未来可能会希望有一些动态数据的存储,而adjustment object希望是一个class default object,不希望在runtime环境下修改数据
scss
USTRUCT()
struct FActiveEffectAdjustmentHandle
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
TObjectPtr<UGameplayEffectAdjustment> EffectAdjustment;
};
然后修改ASC,新增两个TMap
scss
UPROPERTY()
TMap<FActiveGameplayEffectHandle, FActiveEffectAdjustmentHandle> SourceActiveEffectAdjustmentContainer;
UPROPERTY()
TMap<FActiveGameplayEffectHandle, FActiveEffectAdjustmentHandle> TargetActiveEffectAdjustmentContainer;
为了监听GE的添加和移除,需要绑定两个delegate,OnActiveGameplayEffectAddedDelegateToSelf
和OnAnyGameplayEffectRemovedDelegate
。
同时创建两个函数去处理GE添加和移除。
scss
UFUNCTION()
void HandleGameplayEffectAppliedToSelf(UAbilitySystemComponent* Source, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);
UFUNCTION()
void HandleGameplayEffectRemovedFromSelf(const FActiveGameplayEffect& ActiveEffect);
重写InitAbilityActorInfo
和EndPlay
函数
scss
void ULyraAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{
// 监听Effect Applied,用于注册Effect Adjustment
if(!EffectAppliedToSelfHandle.IsValid())
{
EffectAppliedToSelfHandle = OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(this, &ULyraAbilitySystemComponent::HandleGameplayEffectAppliedToSelf);
}
if(!EffectRemovedFromSelfHandle.IsValid())
{
EffectRemovedFromSelfHandle = OnAnyGameplayEffectRemovedDelegate().AddUObject(this, &ULyraAbilitySystemComponent::HandleGameplayEffectRemovedFromSelf);
}
}
void ULyraAbilitySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// 解除监听Effect Applied和removed
if(EffectAppliedToSelfHandle.IsValid())
{
OnGameplayEffectAppliedDelegateToSelf.Remove(EffectAppliedToSelfHandle);
}
if(EffectRemovedFromSelfHandle.IsValid())
{
OnAnyGameplayEffectRemovedDelegate().Remove(EffectRemovedFromSelfHandle);
}
}
HandleGameplayEffectAppliedToSelf
实现
scss
void ULyraAbilitySystemComponent::HandleGameplayEffectAppliedToSelf(UAbilitySystemComponent* Source,
const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle)
{
if(!IsValid(SpecApplied.Def)) return;
ULyraGameplayEffect* EffectCDO = Cast<ULyraGameplayEffect>(SpecApplied.Def);
if(!IsValid(EffectCDO)) return;
if(!EffectCDO->SourceGrantEffectAdjustments.IsEmpty())
{
for(UGameplayEffectAdjustment* Adjustment: EffectCDO->SourceGrantEffectAdjustments)
{
FActiveEffectAdjustmentHandle AdjustmentHandle;
AdjustmentHandle.EffectAdjustment = Adjustment;
SourceActiveEffectAdjustmentContainer.Add(ActiveHandle, AdjustmentHandle);
}
}
if(!EffectCDO->TargetGrantEffectAdjustments.IsEmpty())
{
for(UGameplayEffectAdjustment* Adjustment: EffectCDO->TargetGrantEffectAdjustments)
{
FActiveEffectAdjustmentHandle AdjustmentHandle;
AdjustmentHandle.EffectAdjustment = Adjustment;
TargetActiveEffectAdjustmentContainer.Add(ActiveHandle, AdjustmentHandle);
}
}
}
HandleGameplayEffectRemovedFromSelf
实现
ini
void ULyraAbilitySystemComponent::HandleGameplayEffectRemovedFromSelf(const FActiveGameplayEffect& ActiveEffect)
{
const FActiveGameplayEffectHandle& Handle = ActiveEffect.Handle;
SourceActiveEffectAdjustmentContainer.Remove(Handle);
TargetActiveEffectAdjustmentContainer.Remove(Handle);
}
到这里通过GE给角色添加Effect Adjustment已经完成了,接下来需要实现的是在伤害中获取对伤害的修饰
首先在ASC中添加静态函数TryApplyGameplayEffectAdjustment
arduino
static void TryApplyGameplayEffectAdjustment(TMap<FGameplayTag, float>& AdjustmentData, const FGameplayEffectSpecHandle& EffectSpecHandle,
ULyraAbilitySystemComponent* SourceASC, ULyraAbilitySystemComponent* TargetASC);
实现,逻辑为遍历角色身上的Effect Adjustments,然后将修改的属性写入字典AdjustmentData中
scss
void ULyraAbilitySystemComponent::TryApplyGameplayEffectAdjustment(TMap<FGameplayTag, float>& AdjustmentData,
const FGameplayEffectSpecHandle& EffectSpecHandle, ULyraAbilitySystemComponent* SourceASC,
ULyraAbilitySystemComponent* TargetASC)
{
for(auto& Pair: SourceASC->SourceActiveEffectAdjustmentContainer)
{
FActiveEffectAdjustmentHandle AdjustmentHandle = Pair.Value;
if(AdjustmentHandle.EffectAdjustment)
{
if(AdjustmentHandle.EffectAdjustment->CanApplyEffectAdjustment(EffectSpecHandle, SourceASC, TargetASC))
{
AdjustmentHandle.EffectAdjustment->TryApplyEffectAdjustment(AdjustmentData);
}
}
}
for(auto& Pair: TargetASC->TargetActiveEffectAdjustmentContainer)
{
FActiveEffectAdjustmentHandle AdjustmentHandle = Pair.Value;
if(AdjustmentHandle.EffectAdjustment)
{
if(AdjustmentHandle.EffectAdjustment->CanApplyEffectAdjustment(EffectSpecHandle, SourceASC, TargetASC))
{
AdjustmentHandle.EffectAdjustment->TryApplyEffectAdjustment(AdjustmentData);
}
}
}
}
有了伤害修饰的数据,可以把伤害相关的数据划分,分配不同的tag,比如基础伤害,额外伤害,伤害百分比等
ini
FGameplayTag SetByCaller_Damage = FGameplayTag::RequestGameplayTag("SetByCaller.Damage");
FGameplayTag SetByCaller_ExtraDamage = FGameplayTag::RequestGameplayTag("SetByCaller.ExtraDamage");
FGameplayTag SetByCaller_Rate = FGameplayTag::RequestGameplayTag("SetByCaller.Rate");
然后在伤害计算的Execution中,取出伤害修饰的数据,放入伤害计算中
css
ULyraAbilitySystemComponent::TryApplyGameplayEffectAdjustment(ExecutionHelper.AdjustmentData, SpecHandle, SourceASC, TargetASC);
float DamageExtra = 0.f;
DamageExtra += ExecutionHelper.AdjustmentData.FindRef(FLyraGameplayTags::Get().SetByCaller_ExtraDamage);
伤害动态伤害实例
这里举一个简单的例子,假设有一个愤怒buff,角色生命值低于50%时造成的伤害增加额外值10点
首先创建EffectAdjustment的子类
重写CanApplyEffectAdjustment
函数
当攻击者的生命值低于50%时满足条件,返回true
如果满足条件,需要增加额外伤害10点
最后把实现的Effect Adjustment配置到GameplayEffect中即可