八、RPGDamageExecution.h/cpp & GameplayEffectExecutionCalculation

GameplayEffectExecutionCalculation

参考: GameplayEffectExectutionCalculation

简单来说,GameplayEffectExecutionCalculation就是GameplayEffect拥有的一个函数。它在技能开始时调用,或者是以固定间隔持续调用。
GameplayEffectExecutionCalculation几乎可以胜任所有事情,因为Execute方法提供了必要的所有参数以影响对应的actor, ability system , 甚至系统之外的世界。但是,GameplayEffectExecutionCalculation由于只支持c++编写,且没有ability类似的影响外部世界的机制,通常较难设置。在二者都能解决问题的情况下,我们倾向于使用ability。
GameplayEffectExecutionCalculation的独到之处在于它能捕获技能施放者和目标身上的attributes。当被激活时,GameplayEffectExecutionCalculation可以应用modifier来修改attributes,也可以把attributes当做参数进行计算,也可以snapshot特定的attributes供以后使用(比如:你可以把GameplayEffectSpec挂载在一个火球上,当火球撞击目标后触发效果,此时火球造成的伤害将仅仅由释放时人物的属性决定)。这些功能使得它非常适合全局伤害计算,接下来的会举一个这样的例子。

例子

假设人物的attributes为:

  • Health : 生命值
  • AttackMultiplier :输出伤害加成
  • DefenseMultiplier: 接受伤害比例( 0 ~ 1)
  • BaseAttackPower: 基础伤害值
UCLASS()
class UMyAttributeSet : public UAttributeSet
{
    GENERATED_BODY()

public:

    //Hitpoints. Self-explanatory.
    UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite)
    FGameplayAttributeData Health;
    
    //Outgoing damage-multiplier.
    UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite, meta = (HideFromModifiers))
    FGameplayAttributeData AttackMultiplier;

    //Incoming damage-multiplier.
    UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite)
    FGameplayAttributeData DefenseMultiplier;

    //Base damage of an outgoing attack.
    UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite)
    FGameplayAttributeData BaseAttackPower;
}

我们需要给Execute函数提供CaptureDefinition(需要捕获哪些属性,捕获对象是谁,是否要snapshot?)。因此,定义一个结构体,并使用引擎提供的宏定义:

struct AttStruct
{
    DECLARE_ATTRIBUTE_CAPTUREDEF(Health); //The DECLARE_ATTRIBUTE_CAPTUREDEF macro actually only declares two variables. The variable names are dependent on the input, however. Here they will be HealthProperty(which is a UPROPERTY pointer)
                                                                         //and HealthDef(which is a FGameplayEffectAttributeCaptureDefinition).
    DECLARE_ATTRIBUTE_CAPTUREDEF(AttackMultiplier); //Here AttackMultiplierProperty and AttackMultiplierDef. I hope you get the drill.
    DECLARE_ATTRIBUTE_CAPTUREDEF(DefenseMultiplier);
    DECLARE_ATTRIBUTE_CAPTUREDEF(BaseAttackPower);

    AttStruct()
    {
        // We define the values of the variables we declared now. In this example, HealthProperty will point to the Health attribute in the UMyAttributeSet on the receiving target of this execution. The last parameter is a bool, and determines if we snapshot the attribute's value at the time of definition.
        DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Health, Target, false);

        //This here is a different example: We still take the attribute from UMyAttributeSet, but this time it is BaseAttackPower, and we look at the effect's source for it. We also want to snapshot is because the effect's strength should be determined during its initial creation. A projectile wouldn't change
                //damage values depending on the source's stat changes halfway through flight, after all.
        DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, BaseAttackPower, Source, true);

        //The same rules apply for the multiplier attributes too.
        DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, AttackMultiplier, Source, true);
        DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, DefenseMultiplier, Target, false);
    }
};

之后,我们在GameplayEffectExecutionCalculation的构造函数中填充RelevantAttributesToCapture数组:

UDamageExec::UDamageExec(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
      AttStruct Attributes;
      
      RelevantAttributesToCapture.Add(Attributes.HealthDef); //RelevantAttributesToCapture is the array that contains all attributes you wish to capture, without exceptions. 
      InvalidScopedModifierAttributes.Add(Attributes.HealthDef); //However, an attribute added here on top of being added in RelevantAttributesToCapture will still be captured, but will not be shown for potential in-function modifiers in the GameplayEffect blueprint, more on that later.
      
      RelevantAttributesToCapture.Add(Attributes.BaseAttackPowerDef);
      RelevantAttributesToCapture.Add(Attributes.DefenseMultiplierDef);
      RelevantAttributesToCapture.Add(Attributes.AttackMultiplierDef);
}

最后,重写Execute_Implementation函数:

void UDamageExec::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
    AttStruct Attributes; //Creating the attribute struct, we will need its values later when we want to get the attribute values.

    UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent(); //We put AbilitySystemComponents into little helper variables. Not necessary, but it helps keeping us from typing so much.
    UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent();

    AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->AvatarActor : nullptr; //If our AbilitySystemComponents are valid, we get each their owning actors and put them in variables. This is mostly to prevent crashing by trying to get the AvatarActor variable from
    AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->AvatarActor : nullptr; //a null pointer.

    const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();

    const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
    const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags(); //Some more helper variables: Spec is the spec this execution originated from, and the Source/TargetTags are pointers to the tags granted to source/target actor, respectively.

    FAggregatorEvaluateParameters EvaluationParameters; //We use these tags to set up an FAggregatorEvaluateParameters struct, which we will need to get the values of our captured attributes later in this function.
    EvaluationParameters.SourceTags = SourceTags;
    EvaluationParameters.TargetTags = TargetTags;



    float Health = 0.f;
    //Alright, this is where we get the attribute's captured value into our function. Damage().HealthDef is the definition of the attribute we want to get, we defined EvaluationParameters just above us, and Health is the variable where we will put the captured value into(the Health variable we just declared)
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attributes.HealthDef, EvaluationParameters, Health); 

    float BaseAttackPower = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attribute.BaseAttackPowerDef, EvaluationParameters, BaseAttackPower); // We do this for all other attributes, as well.

    float AttackMultiplier = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attribute.AttackMultiplierDef, EvaluationParameters, AttackMultiplier);

    float DefensePower = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attribute.DefenseMultiplierPowerDef, EvaluationParameters, DefenseMultiplier);



    //Finally, we go through our simple example damage calculation. BaseAttackPower and AttackMultiplier come from soruce, DefensePower comes from target.
    float DamageDone = BaseAttackPower * AttackMultiplier * DefensePower;
    //An optional step is to clamp to not take health lower than 0. This can be ignored, or implemented in the attribute sets' PostGameplayEffectExecution function. Your call, really.
    DamageDone = FMath::Min<float>( DamageDone, Health );

    //Finally, we check if we even did any damage in this whole ordeal. If yes, then we will add an outgoing execution modifer to the Health attribute we got from our target, which is a modifier that can still be thrown out by the attribute system if it wishes to throw out the GameplayEffectExecutionCalculation.
    if (DamageDone > 0.f)
    {
        OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(Attributes.HealthProperty, EGameplayModOp::Additive, -DamageDone));
    }

    //Congratulations, your damage calculation is complete!

RPGDamageExecution.h/cpp

ActionRPG的代码与上面举的例子极其相似,就不展开讲了。

头文件:

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "ActionRPG.h"
#include "GameplayEffectExecutionCalculation.h"
#include "RPGDamageExecution.generated.h"

/**
 * A damage execution, which allows doing damage by combining a raw Damage number with AttackPower and DefensePower
 * Most games will want to implement multiple game-specific executions
 */
UCLASS()
class ACTIONRPG_API URPGDamageExecution : public UGameplayEffectExecutionCalculation
{
    GENERATED_BODY()

public:
    // Constructor and overrides
    URPGDamageExecution();
    virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;

};

源文件:

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "Abilities/RPGDamageExecution.h"
#include "Abilities/RPGAttributeSet.h"
#include "AbilitySystemComponent.h"

struct RPGDamageStatics
{
    DECLARE_ATTRIBUTE_CAPTUREDEF(DefensePower);
    DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
    DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);

    RPGDamageStatics()
    {
        // Capture the Target's DefensePower attribute. Do not snapshot it, because we want to use the health value at the moment we apply the execution.
        DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, DefensePower, Target, false);

        // Capture the Source's AttackPower. We do want to snapshot this at the moment we create the GameplayEffectSpec that will execute the damage.
        // (imagine we fire a projectile: we create the GE Spec when the projectile is fired. When it hits the target, we want to use the AttackPower at the moment
        // the projectile was launched, not when it hits).
        DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, AttackPower, Source, true);

        // Also capture the source's raw Damage, which is normally passed in directly via the execution
        DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, Damage, Source, true);
    }
};

static const RPGDamageStatics& DamageStatics()
{
    static RPGDamageStatics DmgStatics;
    return DmgStatics;
}

URPGDamageExecution::URPGDamageExecution()
{
    RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef);
    RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
    RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
}

void URPGDamageExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
    UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent();
    UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent();

    AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->AvatarActor : nullptr;
    AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->AvatarActor : nullptr;

    const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();

    // Gather the tags from the source and target as that can affect which buffs should be used
    const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
    const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();

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

    // --------------------------------------
    //  Damage Done = Damage * AttackPower / DefensePower
    //  If DefensePower is 0, it is treated as 1.0
    // --------------------------------------

    float DefensePower = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DefensePowerDef, EvaluationParameters, DefensePower);
    if (DefensePower == 0.0f)
    {
        DefensePower = 1.0f;
    }

    float AttackPower = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().AttackPowerDef, EvaluationParameters, AttackPower);

    float Damage = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageDef, EvaluationParameters, Damage);

    float DamageDone = Damage * AttackPower / DefensePower;
    if (DamageDone > 0.f)
    {
        OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone));
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容

  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,764评论 0 27
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,705评论 0 38
  • 姓名:吴明文 企业名称:耀升机电有限公司 组别:反省组 【日精进打卡第62天】 【知~学习】 诵读《大纲》5遍,累...
    吴明文阅读 186评论 0 0
  • 早晨——亲新我家 上午——普惠社区 下午——走近乡村 夜晚——依稀街道
    雨清World阅读 621评论 0 1