本篇文章主要参考EPIC商场的RPG游戏demo。实现GE计算中使用自定义的Calculation Class
新建Attribute
打开AttributeSetBase.h/cpp,增加两个属性Damage和Armor,方法和之前的一样,这里就直接贴代码了。
UCLASS()
class GAS_LEARN_API UAttributeSetBase : public UAttributeSet
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Attributes", ReplicatedUsing=OnRep_Damage)
FGameplayAttributeData Damage;
ATTRIBUTE_ACCESSORS(UAttributeSetBase, Damage);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Attributes", ReplicatedUsing=OnRep_Armor)
FGameplayAttributeData Armor;
ATTRIBUTE_ACCESSORS(UAttributeSetBase, Armor);
UFUNCTION()
virtual void OnRep_Damage(const FGameplayAttributeData& OldDamage);
UFUNCTION()
virtual void OnRep_Armor(const FGameplayAttributeData& OldArmor);
};
复制代码
void UAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Damage, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Armor, COND_None, REPNOTIFY_Always);
}
void UAttributeSetBase::OnRep_Damage(const FGameplayAttributeData& OldDamage)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, Damage, OldDamage);
}
void UAttributeSetBase::OnRep_Armor(const FGameplayAttributeData& OldArmor)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, Armor, OldArmor);
}
复制代码
初始化两个属性。
打开之前创建的DataTable。可能有人没有看过之前的文章,这里我再贴一次流程图。
打开角色蓝图的ASC的Default Settings。
如果不想用DataTable,也可以使用一个gameplay effect赋初值。
然后通过ApplyGameplayEffectToSelf来赋初值。
Custom Calculation Class
新建类
这里我命名为GMM_DamageExecutionCalculation。
实现
打开GMM_DamageExecutionCalculation.h。
这里通过重写方法CalculateBaseMagnitude_Implementation,返回的float值即为计算得到的Magnitude,用于修改指定的Attribute。
UCLASS()
class GAS_LEARN_API UGMM_DamageExecutionCalculation : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UGMM_DamageExecutionCalculation();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
};
复制代码
如果我们打开UGameplayModMagnitudeCalculation.cpp。可以看到被我们重写的函数比较简单,就是返回0值
float UGameplayModMagnitudeCalculation::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
return 0.f;
}
复制代码
GMM_DamageExecutionCalculation.cpp,创建结构体SDamageStatics和构造函数
里面用于存储我们需要获取的Attribute对象。其中Damage需要从施加伤害方获取,Armor需要从被攻击方获取,这个逻辑应该比较容易理解吧。
struct SDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
SDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UAttributeSetBase, Damage, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAttributeSetBase, Armor, Target, true);
}
};
static const SDamageStatics& DamageStatics()
{
static SDamageStatics DStatics;
return DStatics;
}
UGMM_DamageExecutionCalculation::UGMM_DamageExecutionCalculation()
{
RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
}
复制代码
这一部分不影响这里用到了两个Macro方法,分别是DECLARE_ATTRIBUTE_CAPTUREDEF和DEFINE_ATTRIBUTE_CAPTUREDEF。可以查看一下他们的源码。
DECLARE_ATTRIBUTE_CAPTUREDEF定义了两个对象,假设是Damage的,那就是DamageProperty和DamageDef。
DEFINE_ATTRIBUTE_CAPTUREDEF尝试对两个对象赋值。
// -------------------------------------------------------------------------
// Helper macros for declaring attribute captures
// -------------------------------------------------------------------------
#define DECLARE_ATTRIBUTE_CAPTUREDEF(P) \
FProperty* P##Property; \
FGameplayEffectAttributeCaptureDefinition P##Def; \
#define DEFINE_ATTRIBUTE_CAPTUREDEF(S, P, T, B) \
{ \
P##Property = FindFieldChecked<FProperty>(S::StaticClass(), GET_MEMBER_NAME_CHECKED(S, P)); \
P##Def = FGameplayEffectAttributeCaptureDefinition(P##Property, EGameplayEffectAttributeCaptureSource::T, B); \
}
复制代码
然后进一步看看FGameplayEffectAttributeCaptureDefinition结构体有什么对象。这里比较重要的是两个数据,一个是GameplayAttribute,即我们需要捕获的Attribute,还有一个是GameplayEffectAttributeCaptureSource,即Attribute的Source,有这两个就可以成功获得我们需要的属性了。
/** Struct defining gameplay attribute capture options for gameplay effects */
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayEffectAttributeCaptureDefinition
{
GENERATED_USTRUCT_BODY()
/** Gameplay attribute to capture */
UPROPERTY(EditDefaultsOnly, Category=Capture)
FGameplayAttribute AttributeToCapture;
/** Source of the gameplay attribute */
UPROPERTY(EditDefaultsOnly, Category=Capture)
EGameplayEffectAttributeCaptureSource AttributeSource;
};
复制代码
GMM_DamageExecutionCalculation.cpp,实现CalculateBaseMagnitude_Implementation
通过GetCapturedAttributeMagnitude方法获取Damage和Armor的属性值,这里计算最终伤害的方法很简单,就是Damage-Armor。
float UGMM_DamageExecutionCalculation::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
float Damage = 0.0f;
GetCapturedAttributeMagnitude(DamageStatics().DamageDef, Spec, EvaluationParameters, Damage);
float Armor = 0.0f;
GetCapturedAttributeMagnitude(DamageStatics().ArmorDef, Spec, EvaluationParameters, Armor);
return Damage - Armor;
}
复制代码
这里使用的GetCapturedAttributeMagnitude方法实际是一个bool函数,我们可以判断是否成功获取了对应的属性值
bool UGameplayModMagnitudeCalculation::GetCapturedAttributeMagnitude(const FGameplayEffectAttributeCaptureDefinition& Def, const FGameplayEffectSpec& Spec, const FAggregatorEvaluateParameters& EvaluationParameters, OUT float& Magnitude) const
{
const FGameplayEffectAttributeCaptureSpec* CaptureSpec = Spec.CapturedRelevantAttributes.FindCaptureSpecByDefinition(Def, true);
if (CaptureSpec == nullptr)
{
ABILITY_LOG(Error, TEXT("GetCapturedAttributeMagnitude unable to get capture spec."));
return false;
}
if (CaptureSpec->AttemptCalculateAttributeMagnitude(EvaluationParameters, Magnitude) == false)
{
ABILITY_LOG(Error, TEXT("GetCapturedAttributeMagnitude unable to calculate attribute magnitude."));
return false;
}
return true;
}
复制代码
测试
自己设置一个gameplay effect。测试一下,设置的Damge=50,Armor=20,造成的伤害为30,没有问题。
Execution Calculation Class
Exeution类的实现方法和自定义计算单元类似,区别是对attribute的修改时直接在类中实现了,而不是返回一个float修改量。
新建类
实现
打开UGEDamageExecutionCalculation.h
和之前唯一的区别是重写了Execute_Implementation函数。
UCLASS()
class GAS_LEARN_API UGEDamageExecutionCalculation : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UGEDamageExecutionCalculation();
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
复制代码
打开UGEDamageExecutionCalculation.cpp
在这里因为需要直接修改Target的Health Attribute,所以还需要声明一个Health Attribute。
struct SDmageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(Health)
SDmageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UAttributeSetBase, Damage, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAttributeSetBase, Armor, Target, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAttributeSetBase, Health, Target, true);
}
};
static const SDmageStatics& DamageStatics()
{
static SDmageStatics DStatics;
return DStatics;
}
UGEDamageExecutionCalculation::UGEDamageExecutionCalculation()
{
RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().HealthDef);
}
复制代码
在Execute_Implementation中,我们计算获得最终的伤害后,通过OutExecutionOutput直接修改了Target的Health。在Execution中我可以通过这种方法同时修改多个Attribute,可以同时修改Target和Source的属性。
更重要的是,这里我们获取了Target或Source的AbilitySystemComponent,我们可以通过delegate或者ASC中的public function直接向目标的ASC发送信息,调用函数。所以Execution可以实现的效果是丰富的。
void UGEDamageExecutionCalculation::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
UAbilitySystemComponent* TargetAbilitySystem = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceAbilitySystem = ExecutionParams.GetSourceAbilitySystemComponent();
AActor* TargetActor = TargetAbilitySystem ? TargetAbilitySystem->GetAvatarActor() : nullptr;
AActor* SourceActor = SourceAbilitySystem ? SourceAbilitySystem->GetAvatarActor() : nullptr;
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
float Armor = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvaluationParameters, Armor);
float Damage = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageDef, EvaluationParameters, Damage);
float UnmitigatedDamage = Damage;
float MitigatedDamage = UnmitigatedDamage - Armor;
if(MitigatedDamage > 0.0f)
{
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(DamageStatics().HealthProperty, EGameplayModOp::Additive, -MitigatedDamage));
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(DamageStatics().ArmorProperty, EGameplayModOp::Additive, -10.0f));
}
}
复制代码
测试
同样创建一个Gameplay Effect。可以看到角色的属性中Health减少了30,Armor也减少了10。
在这里我们还可以通过Calculation Modifiers对输入的属性在运算前进一步的修改。我们可以让Damage在计算前先增加10。可以看到这次最终的伤害变为40了,让health减小为60
我们可以看到一个Execution同样可以拥有modifier和Conditional GE,其中的设置和modifier几乎一致,意味着我们可以对单个Execution进一步进行一些复杂的设计和计算。
OK,到这里这一篇的内容就完成了,下一篇会讲解GE的剩余部分。耐心看完,你一定会有所收获(笑)