伤害计算
我们在前面测试GA
的时候已经简单测试了伤害,不过那个伤害就固定的一个值, 下面我们用自定义的类GEEC
来计算这个伤害
1
| USRGEEC_Damage : public UGameplayEffectExecutionCalculation
|
给上我们的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() { 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();
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)); } }
|
测试
运行正常,每次伤害为8(10-2)
自动回复
我们属性里面有设置生命值和魔法值, 那么一般情况下这两种属性都有自动回复的机制,所以我们用GE
来实现这个效果
配置两个GE
到我们的玩家角色的默认启动的GE
内
设置GE
如上图, 设定持续事件为Infinite
,计算方式是基于其他属性, 然后设置Period
=1, 即每1秒执行一次
在下面的移除标签内加上死亡状态, 即死亡了以后会移除自动回复效果
接下来简单测试一下添加了死亡tag
以后的情况
我们用一个测试GE
什么都不做,就添加一个Death
标签
搞定!
但是问题又来了, 这个生命回复到超过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
参数,如下图
此举意味着我们计算时的Attack
值会额外增加对应等级的攻击力
CSV
表格的编辑如下
然后可以得到不同等级对应的攻击力加成
伤害计算中的属性关联
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
类内申明几个用于计算伤害的临时属性, 在GASD
和ActionRPG
项目中都采用了如此的方案
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 , float PhysicsResistance )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 , float Resistance )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
类, 我们同时用一张表格来配置技能对应等级的系数
测试样本: 初始10攻击力, 目标护甲10, 物理抗性10, 伤害减免10%
分别用1级技能和10级技能攻击测试物理部分
完成
伤害进阶处理:闪避和格挡
接下来我们再考虑加入闪避和格挡因素,这两者一般游戏针对的都是物理攻击,当然不排除也有闪避法术的设定,我们这里采用的是前者
还需要考虑一个设定, 格挡是格挡税前伤害还是税后, 即伤害减免是在格挡的前后问题,我们使用在税后(伤害减免后)在进行格挡计算, 这种情况下格挡的收益比较大
因为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)); } }
|
测试
完成!
伤害进阶处理: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;
|
补充一个属性图, 我们初始化的时候给了50%的火系加成
测试
重点看税前已经变成1.5的伤害了, 正确