GameplayAbilitySystem入门与实战(七):GameplayEffect(二)

前言

上一篇讲述了GE的一些基本概念, 非常概念化没有实战部分, 很容易混淆和忘记, 本片我们结合实际项目再配合GA的使用来模拟几个技能效果

伤害计算

我们在前面测试GA的时候已经简单测试了伤害,不过那个伤害就固定的一个值, 下面我们用自定义的类GEEC来计算这个伤害

1
USRGEEC_Damage : public UGameplayEffectExecutionCalculation

image-20201218172355966

给上我们的GEEC

GEEC中只需要重写方法

1
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;

然后我们需要捕获我们角色的一些数据, 比如我们捕获我们发起者的攻击以及目标的护甲

为了方便使用, 我们自定义一个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct  SRDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Attack);
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(Health);


SRDamageStatics()
{
//攻击力在创建GE时捕获, 因为技能可能有弹道, 需要在发射的时候就捕获而不是击中的时候,
//类似的其他有关发射者的属性都所需要此时捕获
DEFINE_ATTRIBUTE_CAPTUREDEF(USRAttributeSetBase, Attack, Source, true);
//目标的防御在应用的时候捕获
DEFINE_ATTRIBUTE_CAPTUREDEF(USRAttributeSetBase, Armor, Target, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(USRAttributeSetBase, Health, Target, false);
}

};
1
2
3
4
5
static const SRDamageStatics& DamageStatics()
{
static SRDamageStatics DmgStatics;
return DmgStatics;
}

然后设置捕获

1
2
3
4
5
6
USRGEEC_Damage::USRGEEC_Damage()
{
RelevantAttributesToCapture.Add(DamageStatics().AttackDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().HealthDef);
}

后面就是加加减减一下最终反馈到目标的血量(临时方案)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void USRGEEC_Damage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent();

AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->GetAvatarActor() : nullptr;
AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->GetAvatarActor() : nullptr;

const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();

// 获取双方所有tag
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();

FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;


float ArmorPower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvaluationParameters, ArmorPower);

float AttackValue = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().AttackDef, EvaluationParameters, AttackValue);

float DamageDone = -1 * (AttackValue - ArmorPower);
if (DamageDone < 0.f)
{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().HealthProperty, EGameplayModOp::Additive, DamageDone));
}
}

测试

image-20201218175903444

录制_2020_12_18_17_57_55_376

运行正常,每次伤害为8(10-2)

自动回复

我们属性里面有设置生命值和魔法值, 那么一般情况下这两种属性都有自动回复的机制,所以我们用GE来实现这个效果

image-20201221093148509

配置两个GE到我们的玩家角色的默认启动的GE

image-20201221093257062

设置GE如上图, 设定持续事件为Infinite,计算方式是基于其他属性, 然后设置Period=1, 即每1秒执行一次

image-20201221093407727

在下面的移除标签内加上死亡状态, 即死亡了以后会移除自动回复效果

接下来简单测试一下添加了死亡tag以后的情况

我们用一个测试GE什么都不做,就添加一个Death标签

录制_2020_12_21_09_50_34_192

搞定!

但是问题又来了, 这个生命回复到超过MaxHealth的时候就不对了, 我们必须在特定的地方对此做一个限制

属性Clamp

属性篇中我们提过一个PostGameplayEffectExecute()函数, 用此函数就是官方建议我们来做属性Clamp处理的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void USRAttributeSetBase::PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data)
{
UE_LOG(SRLog, Log, TEXT("PostGameplayEffectExecute "));
Super::PostGameplayEffectExecute(Data);

FGameplayEffectContextHandle Context = Data.EffectSpec.GetContext();
UAbilitySystemComponent* SourceASC = Context.GetOriginalInstigatorAbilitySystemComponent();
const FGameplayTagContainer& SourceTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();
FGameplayTagContainer SpecAssetTags;
Data.EffectSpec.GetAllAssetTags(SpecAssetTags);

AActor* TargetActor = nullptr;
AController* TargetController = nullptr;
ASRCharacterBase* TargetCharacter = nullptr;
if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
{
TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
TargetCharacter = Cast<ASRCharacterBase>(TargetActor);
}

AActor* SourceActor = nullptr;
AController* SourceController = nullptr;
ASRCharacterBase* SourceCharacter = nullptr;
if (SourceASC && SourceASC->AbilityActorInfo.IsValid() && SourceASC->AbilityActorInfo->AvatarActor.IsValid())
{
SourceActor = SourceASC->AbilityActorInfo->AvatarActor.Get();
SourceController = SourceASC->AbilityActorInfo->PlayerController.Get();
if (SourceController == nullptr && SourceActor != nullptr)
{
if (APawn* Pawn = Cast<APawn>(SourceActor))
{
SourceController = Pawn->GetController();
}
}
if (SourceController)
{
SourceCharacter = Cast<ASRCharacterBase>(SourceController->GetPawn());
}
else
{
SourceCharacter = Cast<ASRCharacterBase>(SourceActor);
}
if (Context.GetEffectCauser())
{
SourceActor = Context.GetEffectCauser();
}


if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));
}
else if (Data.EvaluatedData.Attribute == GetManaAttribute())
{
SetMana(FMath::Clamp(GetMana(), 0.0f, GetMaxMana()));
}
}
}

前面茫茫多的变量获取是为了后续做更多的内容, 如伤害处理等, 对于生命和魔法值的Clamp简单处理即可

注意

Source是发起者

Target是作用目标, 即被修改属性的角色,

两者可能会相同, 容易混淆概念

伤害随等级增加

前面我们用一个GEEC通过攻击-防御得到最后的生命值

我们继续扩展, 在下面的ModifierMagnitude内加入参数, 再给一个CurveTable参数,如下图

image-20201222135917433

此举意味着我们计算时的Attack值会额外增加对应等级的攻击力

CSV表格的编辑如下

image-20201222140009303

然后可以得到不同等级对应的攻击力加成

伤害计算中的属性关联

PostGameplayEffectExecute()函数会在引用GE以后调用, 同时也会触发属性改变的事件响应,

同时这里需要注意, 我们的Set***()函数能触发事件响应.

但是无法再次触发PostGameplayEffectExecute()

意味着,加入我们希望通过修改A属性后来修改B属性可以在PostGameplayEffectExecute()函数内处理

比如, Attack属性=AttackA+AttackB, 那么可以在PostGameplayEffectExecute()函数内实现

1
2
3
4
if (Data.EvaluatedData.Attribute == GetAttackAAttribute() || ...)
{
SetAttack(GetAttackA()+GetAttackB());
}

类似这种形式也可以触发Attack的事件响应, 方便UI的同步显示效果

在案例里面, 我设置了5个主属性,如力量,敏捷

随着主属性的修改, 其他副属性如攻击力,护甲值,暴击率等会随之改变, 这些操作是可以放到PostGameplayEffectExecute()内处理的

可否把上述内容放到PreAttributeChange()内?

PreAttributeChange()只是对属性的预处理, 此函数内不适合去Get其他函数的值, 不能保证此时这个值是否正确

PreAttributeChange()比较适合对确定的某些属性最一些自适应处理, 如前面已经讲过的对当前生命值和最大生命值做处理函数AdjustAttributeForMaxChange()

伤害进阶处理

现在有这么一个需求,先不考虑闪避, 格挡等因素, 但需要计算暴击

玩家技能1:

  • 造成攻击力若干百分比的物理伤害
  • 造成攻击力若干百分比的火系伤害

计算方式需求:

  • 物理伤害需计算护甲, 物理抗性等
  • 法系伤害需计算相对应的法术抗性

首先需要在Set类内申明几个用于计算伤害的临时属性, 在GASDActionRPG项目中都采用了如此的方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//临时攻击数据, 通过技能初始化, 用于结算最终伤害
UPROPERTY(BlueprintReadOnly, Category = "AttackTemp", ReplicatedUsing = OnRep_Attack_Physics)
FGameplayAttributeData Attack_Physics;
ATTRIBUTE_ACCESSORS(USRAttributeSetBase, Attack_Physics)
UPROPERTY(BlueprintReadOnly, Category = "AttackTemp", ReplicatedUsing = OnRep_Attack_Fire)
FGameplayAttributeData Attack_Fire;
ATTRIBUTE_ACCESSORS(USRAttributeSetBase, Attack_Fire)

//临时受伤数据
UPROPERTY(BlueprintReadOnly, Category = "DamageTemp", ReplicatedUsing = OnRep_Damage_Physics)
FGameplayAttributeData Damage_Physics;
ATTRIBUTE_ACCESSORS(USRAttributeSetBase, Damage_Physics)
UPROPERTY(BlueprintReadOnly, Category = "DamageTemp", ReplicatedUsing = OnRep_Damage_Fire)
FGameplayAttributeData Damage_Fire;
ATTRIBUTE_ACCESSORS(USRAttributeSetBase, Damage_Fire)

前面我们已经创建了一个GEEC, 然后我们进行扩展

已略过属性捕捉部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void USRGEEC_Damage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
float PhysicsAttack = 0.f;
float FireAttack = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().Attack_PhysicsDef, EvaluationParameters, PhysicsAttack);
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().Attack_FireDef, EvaluationParameters, FireAttack);

float critPro = 0.f;
float critMul = 1.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().CritProDef, EvaluationParameters, critPro);
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().CritMulDef, EvaluationParameters, critMul);

float ArmorValue = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvaluationParameters, ArmorValue);

float PhysicsResistance = 0.f;
float FireResistance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().Resistance_PhysicsDef, EvaluationParameters, PhysicsResistance); ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().Resistance_FireDef, EvaluationParameters, FireResistance);

float PhysicsDamage = CalcPhysicsMitigatedDamage(PhysicsAttack, ArmorValue, DamageReduceValue, PhysicsResistance);
float FireDamage = CalcMagicMitigatedDamage(FireAttack, DamageReduceValue, FireResistance);
}

我们捕获了所需要的属性, 同时通过两个辅助函数计算物理和法术伤害

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
float USRGEEC_Damage::CalcPhysicsMitigatedDamage_Implementation(float UnmitigatedDamage, float TargetArmor, float critPro, float critMul, float ReducePercent /*= 0.0f*/, float PhysicsResistance /*= 0.0f*/)const
{
float outDamage = 0.0f;
bool bIsCrit = UKismetMathLibrary::RandomBoolWithWeight(critPro);
float armorPerc = 1 - (TargetArmor / (TargetArmor + 500));
float globlePerc = FMath::Clamp((1 - ReducePercent), 0.01f, 100.f);
float resistancePerc = 1 - (PhysicsResistance / (PhysicsResistance + 50));
float critValue = FMath::Clamp((bIsCrit ? critMul : 1.0f),1.0f,999.0f);

outDamage = UnmitigatedDamage * armorPerc * globlePerc * resistancePerc * critValue;
if (bDebug && outDamage>0)
{
UE_LOG(SRLog, Log, TEXT("USRGEEC_Damage::CalcPhysicsMitigatedDamage, SourceDamage = %f, ArmorReduce = %f, GlobleReduce = %f, ResistanceReduce = %f , CritValue = %f, \n OutDamage = %f"),
UnmitigatedDamage, 1 - armorPerc, 1 - globlePerc, 1 - resistancePerc, critValue, outDamage);
}
return outDamage;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
float USRGEEC_Damage::CalcMagicMitigatedDamage_Implementation(float UnmitigatedDamage, float critPro, float critMul, float ReducePercent /*= 0.0f*/, float Resistance /*= 0.0f*/)const
{
float outDamage = 0.0f;
bool bIsCrit = UKismetMathLibrary::RandomBoolWithWeight(critPro);
float globlePerc = FMath::Clamp((1 - ReducePercent), 0.01f, 100.f);
float resistancePerc = 1 - (Resistance / (Resistance + 50));
float critValue = FMath::Clamp((bIsCrit ? critMul : 1.0f), 1.0f, 999.0f);

outDamage = UnmitigatedDamage * globlePerc * resistancePerc * critValue;
if (bDebug && outDamage > 0)
{
UE_LOG(SRLog, Log, TEXT("USRGEEC_Damage::CalcMagicMitigatedDamage, SourceDamage = %f, GlobleReduce = %f, ResistanceReduce = %f , CritValue = %f \n OutDamage = %f"),
UnmitigatedDamage, 1 - globlePerc, 1 - resistancePerc, critValue, outDamage);
}
return outDamage;
}

然后添加到修改

1
2
3
4
5
6
7
8
9
if (PhysicsDamage>0)
{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().Damage_PhysicsProperty, EGameplayModOp::Additive, PhysicsDamage));
}

if (FireDamage>0)
{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().Damage_FireProperty, EGameplayModOp::Additive, FireDamage));
}

接下来会调用到目标单位的AttributeSet类中, 用一个函数HandlDamage()和宏DAMAGE_HANDLE来处理伤害和扣血

1
2
3
4
5
6
7
8
9
10
11
void USRAttributeSetBase::HandlDamage(FGameplayAttribute Attribute, ASRCharacterBase* TargetCharacter)
{
float damage, newHp;
DAMAGE_HANDLE(Attribute, TargetCharacter, Physics, damage, newHp);
DAMAGE_HANDLE(Attribute, TargetCharacter, Fire, damage, newHp);
DAMAGE_HANDLE(Attribute, TargetCharacter, Ice, damage, newHp);
DAMAGE_HANDLE(Attribute, TargetCharacter, Poison, damage, newHp);
DAMAGE_HANDLE(Attribute, TargetCharacter, Electricity, damage, newHp);
DAMAGE_HANDLE(Attribute, TargetCharacter, Holy, damage, newHp);
DAMAGE_HANDLE(Attribute, TargetCharacter, Arcane, damage, newHp);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define DAMAGE_HANDLE(Attribute,C,Name,TempDamage,TempNewHp) \
{\
if(Attribute == GetDamage_##Name##Attribute()) \
{ \
TempDamage = GetDamage_##Name(); \
SetDamage_##Name(0); \
if(TempDamage>0) \
{ \
ASRCharacterBase* character = Cast<ASRCharacterBase>(C); \
if (character&&character->IsAlive())\
{\
TempNewHp = GetHealth() - TempDamage;\
SRLOGEX2(TEXT("USRAttributeSetBase::HandlDamage %s beDamage = %f"), *TargetCharacter->GetName(), TempDamage);\
SetHealth(TempNewHp);\
}\
} \
} \
}

然后是设置测试GE类, 我们同时用一张表格来配置技能对应等级的系数

image-20201225172537711

image-20201225172450545

测试样本: 初始10攻击力, 目标护甲10, 物理抗性10, 伤害减免10%

分别用1级技能和10级技能攻击测试物理部分

  • 1级

image-20201225173327395

  • 10级

image-20201225173355889

完成

伤害进阶处理:闪避和格挡

接下来我们再考虑加入闪避和格挡因素,这两者一般游戏针对的都是物理攻击,当然不排除也有闪避法术的设定,我们这里采用的是前者

还需要考虑一个设定, 格挡是格挡税前伤害还是税后, 即伤害减免是在格挡的前后问题,我们使用在税后(伤害减免后)在进行格挡计算, 这种情况下格挡的收益比较大

因为GEEC不适合做格挡和闪避的效果处理, 比如播放动画之类的, 那么此类消息我们打算传递给ASC去做处理或者转发

ASC中申明几个代理和函数

1
2
3
4
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRecieveEventDodge, USRAbilitySystemComponent*, SourceASC, const FString&, ExtendInfo);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnReceiveEventBlock, USRAbilitySystemComponent*, SourceASC, const FHitResult&, HitResult, float, UnmitigatedDamage,float,BlockValue);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnReceiveEventDamage, USRAbilitySystemComponent*, SourceASC, const FHitResult&, HitResult, float, UnmitigatedDamage,float, MitigatedDamage, ESRDamageType,DamageType);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void USRAbilitySystemComponent::ReceiveDodge(USRAbilitySystemComponent* SourceASC, const FString& ExtendInfo)
{
if (bDebugLog)
{
SRLOGEX2(TEXT("%s 闪避了 %s 的攻击"), *GetAvatarActor()->GetName(), *SourceASC->GetAvatarActor()->GetName());
}

OnDodge.Broadcast(SourceASC, ExtendInfo);
}




void USRAbilitySystemComponent::ReceiveDamage(USRAbilitySystemComponent* SourceASC, const FHitResult& HitResult, float UnmitigatedDamage, float MitigatedDamag, ESRDamageType DamageType)
{
if (bDebugLog)
{
SRLOGEX5(TEXT("%s 收到 %s 的%s攻击, 税前伤害 = %f, 税后= %f"), *GetAvatarActor()->GetName(), *SourceASC->GetAvatarActor()->GetName(),
*UFlib_SRUtilities::DamageTypeToString(DamageType),UnmitigatedDamage, MitigatedDamag);
}

OnDamage.Broadcast(SourceASC, HitResult, UnmitigatedDamage, MitigatedDamag, DamageType);
}

void USRAbilitySystemComponent::ReceiveBlock(USRAbilitySystemComponent* SourceASC, const FHitResult& HitResult, float UnmitigatedDamage, float BlockValue)
{
if (bDebugLog)
{
SRLOGEX4(TEXT("%s 格挡了 %s 攻击的 %f 点伤害(格挡前伤害= %f)"), *GetAvatarActor()->GetName(), *SourceASC->GetAvatarActor()->GetName(), BlockValue, UnmitigatedDamage);
}
OnBlock.Broadcast(SourceASC, HitResult, UnmitigatedDamage,BlockValue);
}

这里用到了一个辅助函数, 我们在一个函数库中做了枚举和字符串的转换(蓝图自带的)

1
2
3
4
5
6
7
8
FString UFlib_SRUtilities::DamageTypeToString(ESRDamageType type)
{
return SREnumToString<ESRDamageType>("ESRDamageType", type, true);
}
ESRDamageType UFlib_SRUtilities::StringToDamageType(const FString& name)
{
return SRStringToEnum<ESRDamageType>("ESRDamageType", name);
}

然后就是GEEC中额外加一些代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//闪避和格挡只能作用到物理伤害
if (PhysicsDamage>0)
{
//先计算闪避
float dodge = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DodgeProDef, EvaluationParameters, dodge);
if (dodge > 0)
{
bool bIsMiss = UKismetMathLibrary::RandomBoolWithWeight(dodge);
if (bIsMiss)
{
if (TargetAbilitySystemComponent&&SourceAbilitySystemComponent)
{
//如果闪避了, 那么不需要计算具体伤害
TargetAbilitySystemComponent->ReceiveDodge(SourceAbilitySystemComponent);
return;
}
}
}

//再计算格挡;格挡计算在税后,如果是税前那么需要在计算物理伤害之前进行;
//有些设定需要正面才能格挡, 那么还需要计算方向
float blockPro = 0.0f;
float blockValue = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().BlockProDef, EvaluationParameters, blockPro);
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().BlockValueDef, EvaluationParameters, blockValue);
if (blockPro > 0 && blockValue>0)
{
bool bBlocked = UKismetMathLibrary::RandomBoolWithWeight(blockPro);
if (bBlocked)
{
if (blockValue>=PhysicsDamage)
{
TargetAbilitySystemComponent->ReceiveBlock(SourceAbilitySystemComponent, hit, PhysicsDamage, blockValue);
}
else
{
float Unmi = PhysicsDamage;
PhysicsDamage = PhysicsDamage - blockValue;
TargetAbilitySystemComponent->ReceiveBlock(SourceAbilitySystemComponent, hit, Unmi,blockValue);
TargetAbilitySystemComponent->ReceiveDamage(SourceAbilitySystemComponent, hit, PhysicsAttack, PhysicsDamage,ESRDamageType::PHYSICS);
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().Damage_PhysicsProperty, EGameplayModOp::Additive, PhysicsDamage));
}
}
}
else
{
TargetAbilitySystemComponent->ReceiveDamage(SourceAbilitySystemComponent, hit, PhysicsAttack,PhysicsDamage, ESRDamageType::PHYSICS);
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().Damage_PhysicsProperty, EGameplayModOp::Additive, PhysicsDamage));
}
}

测试

image-20201226110601324

完成!

伤害进阶处理:MMC

需求:

法术技能有额外加成, 比如某个装备上有词缀(火系伤害+50%),那么问题来了,

我们之前测试的火系技能伤害最后就变成是 FireDamage = 攻击力 * 等级系数 * (1 + 火系加成)

如果还是用AttributeBase基于属性的方式是不行了, 这里我们用一个自定义的MMC类就比较方便了

上一篇已经大概讲了,MMC需要封装几个蓝图函数方便使用, 不然就去cpp

1
2
3
4
5
6
7
8
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MMC")
AActor* GetInstigator(const FGameplayEffectSpec& Spec)const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MMC")
FGameplayAttribute GetAttributeFromDef(const FGameplayEffectAttributeCaptureDefinition& _Def)const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MMC")
float TryGetCapturedAttributeMagnitude(const FGameplayEffectAttributeCaptureDefinition& Def, const FGameplayEffectSpec& Spec)const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MMC")
float GetLevel(const FGameplayEffectSpec& Spec)const;

image-20201226113630345

补充一个属性图, 我们初始化的时候给了50%的火系加成

image-20201226113716170

测试

image-20201226113752690

重点看税前已经变成1.5的伤害了, 正确