Execution Calculations¶
When a single modifier on a single attribute isn't enough, Execution Calculations let you write arbitrary C++ that reads multiple attributes, performs complex logic, and outputs multiple attribute modifications — all within a single GE execution.
When to Use Execution Calculations¶
Execution Calculations (UGameplayEffectExecutionCalculation, often called "ExecCalcs") are the right tool when:
- Your formula reads from multiple attributes on the source and/or target
- You need to modify multiple attributes in one pass (e.g., damage reduces Health and applies Stagger)
- The logic is too complex for a single modifier's magnitude calculation
- You need conditional output — different attribute changes based on runtime conditions
The classic use case is a damage formula where the final damage depends on the attacker's Attack, the defender's Armor, elemental resistances, critical hit chance, and so on.
ExecCalc vs. Custom Magnitude Calculation
A UGameplayModMagnitudeCalculation produces one number for one modifier. An UGameplayEffectExecutionCalculation can read many attributes and output many modifications. If you only need one number, use CustomCalculationClass. If you need a multi-input, multi-output formula, use an ExecCalc.
Attribute Capture¶
Before an ExecCalc can read attributes, those attributes must be captured. Capture means the engine grabs the attribute's value (or a reference to its aggregator) and makes it available inside the calculation.
Capture Definitions¶
You declare what to capture in the constructor using the DECLARE_ATTRIBUTE_CAPTUREDEF and DEFINE_ATTRIBUTE_CAPTUREDEF helper macros:
// In the header or a shared static struct
struct FDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(Health);
FDamageStatics()
{
// Capture AttackPower from the Source, snapshotted at spec creation
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, AttackPower, Source, true);
// Capture Armor from the Target, live (not snapshotted)
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Armor, Target, false);
// Capture Health from the Target, live
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Health, Target, false);
}
};
static const FDamageStatics& DamageStatics()
{
static FDamageStatics Statics;
return Statics;
}
Source vs. Target¶
- Source — The actor that applied the effect (the attacker, the healer, etc.)
- Target — The actor receiving the effect (the one being damaged, healed, etc.)
Snapshot vs. Live¶
- Snapshot (
true) — The attribute value is captured once when theFGameplayEffectSpecis created and frozen. Even if the source's attribute changes between spec creation and application, the captured value stays the same. - Live (
false) — The attribute value is read at the time the calculation actually executes. This means it reflects the most current state.
When to snapshot: Source attributes (like AttackPower) are commonly snapshotted because the attacker's stats at the moment they "decide" to attack is what should matter.
When to use live: Target attributes (like Armor) are commonly live because the target's state at the moment of impact is what should matter.
Snapshot Default
There's no universal rule. Pick based on your game's design. Snapshotting source and reading target live is common, but some games snapshot everything for determinism.
Registering Captures¶
In your ExecCalc constructor, register the capture definitions with the engine:
UMyDamageExecCalc::UMyDamageExecCalc()
{
RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().HealthDef);
}
The Execute Function¶
Override Execute_Implementation to write your calculation:
void UMyDamageExecCalc::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// ... your logic here ...
}
FGameplayEffectCustomExecutionParameters¶
The input parameter gives you access to:
| Method | Returns |
|---|---|
GetOwningSpec() |
The FGameplayEffectSpec that triggered this execution |
GetTargetAbilitySystemComponent() |
The target ASC |
GetSourceAbilitySystemComponent() |
The source ASC (can be null) |
GetPassedInTags() |
Tags passed in via the execution definition |
AttemptCalculateCapturedAttributeMagnitude(...) |
Evaluate a captured attribute |
AttemptCalculateCapturedAttributeBaseValue(...) |
Get the base value of a captured attribute |
AttemptCalculateCapturedAttributeBonusMagnitude(...) |
Get the bonus (modifiers only) of a captured attribute |
AttemptCalculateTransientAggregatorMagnitude(...) |
Evaluate a transient "temporary variable" aggregator |
FGameplayEffectCustomExecutionOutput¶
The output struct is where you add the attribute modifications your calculation produces:
// Add a modifier to reduce Health by computed damage
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(
DamageStatics().HealthProperty,
EGameplayModOp::Additive,
-FinalDamage
)
);
You can add as many output modifiers as needed. Each one specifies which attribute to modify, which operation to use, and the magnitude.
Additional output control:
| Method | What It Does |
|---|---|
MarkStackCountHandledManually() |
Tell the engine you've already accounted for stack count in your calculation |
MarkConditionalGameplayEffectsToTrigger() |
Allow conditional effects on the execution definition to fire |
MarkGameplayCuesHandledManually() |
Tell the engine you've already triggered gameplay cues yourself |
Scoped Modifiers (Calculation Modifiers)¶
The FGameplayEffectExecutionDefinition that references your ExecCalc class can also specify CalculationModifiers — modifiers that are folded into attribute evaluation only for the scope of this execution. They don't permanently modify the attribute.
This lets designers tweak captured attribute values in the GE asset without changing your C++ code. For example, a GE could specify a scoped +20 bonus to AttackPower that only applies within the damage calculation.
Scoped modifiers can target either captured attributes or transient aggregators — temporary variables identified by gameplay tags that exist only during the execution.
// Transient aggregator example: reading a "temporary variable"
float BonusDamage = 0.f;
ExecutionParams.AttemptCalculateTransientAggregatorMagnitude(
FGameplayTag::RequestGameplayTag("Calc.BonusDamage"),
EvalParams,
BonusDamage);
Passing Data into Execution Calculations¶
There are several ways to get runtime data into your ExecCalc:
1. SetByCaller¶
The most common approach. Set values on the spec before application, read them inside the ExecCalc:
// Setting (before apply)
SpecHandle.Data->SetSetByCallerMagnitude(DamageTag, 100.f);
// Reading (inside Execute_Implementation)
float BaseDamage = ExecutionParams.GetOwningSpec().GetSetByCallerMagnitude(DamageTag);
2. Scoped Modifiers (Backing Data Attribute)¶
GE designers can add CalculationModifiers that target captured attributes. These are automatically folded into AttemptCalculateCapturedAttributeMagnitude.
3. Scoped Modifiers (Backing Data Temp Variable)¶
Same as above but targeting transient aggregators identified by tags. Read via AttemptCalculateTransientAggregatorMagnitude.
4. Effect Context¶
The FGameplayEffectContext (accessible via GetOwningSpec().GetEffectContext()) carries the instigator, effect causer, hit result, origin, and any custom data you've added by subclassing it.
const FGameplayEffectContextHandle& ContextHandle = ExecutionParams.GetOwningSpec().GetEffectContext();
const FHitResult* HitResult = ContextHandle.GetHitResult();
if (HitResult)
{
// Use hit location for headshot detection, etc.
}
Complete Damage Calculation Example¶
Here's a full ExecCalc implementation showing a realistic damage formula:
// DamageExecCalc.h
#pragma once
#include "GameplayEffectExecutionCalculation.h"
#include "DamageExecCalc.generated.h"
UCLASS()
class MYGAME_API UDamageExecCalc : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UDamageExecCalc();
virtual void Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
// DamageExecCalc.cpp
#include "DamageExecCalc.h"
#include "MyAttributeSet.h"
struct FDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
DECLARE_ATTRIBUTE_CAPTUREDEF(CritChance);
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(Health);
FDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, AttackPower, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, CritChance, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Armor, Target, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Health, Target, false);
}
};
static const FDamageStatics& DamageStatics()
{
static FDamageStatics Statics;
return Statics;
}
UDamageExecCalc::UDamageExecCalc()
{
RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
RelevantAttributesToCapture.Add(DamageStatics().CritChanceDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().HealthDef);
}
void UDamageExecCalc::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
// Gather tags for evaluation
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = SourceTags;
EvalParams.TargetTags = TargetTags;
// Read captured attributes
float AttackPower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
DamageStatics().AttackPowerDef, EvalParams, AttackPower);
float CritChance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
DamageStatics().CritChanceDef, EvalParams, CritChance);
float Armor = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
DamageStatics().ArmorDef, EvalParams, Armor);
// Read SetByCaller base damage
static FGameplayTag DamageTag = FGameplayTag::RequestGameplayTag("SetByCaller.Damage");
float BaseDamage = Spec.GetSetByCallerMagnitude(DamageTag, false, 0.f);
// --- Damage Formula ---
// Raw damage = base damage + attack power contribution
float RawDamage = BaseDamage + (AttackPower * 0.5f);
// Critical hit check
float CritMultiplier = 1.0f;
if (FMath::FRand() < FMath::Clamp(CritChance / 100.f, 0.f, 1.f))
{
CritMultiplier = 2.0f;
}
// Armor mitigation: damage reduced by Armor / (Armor + 100)
float ArmorMitigation = 1.0f - (Armor / (Armor + 100.f));
float FinalDamage = RawDamage * CritMultiplier * ArmorMitigation;
FinalDamage = FMath::Max(FinalDamage, 0.f); // No negative damage
// Output: reduce Health
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(
DamageStatics().HealthProperty,
EGameplayModOp::Additive,
-FinalDamage));
}
Then in the GE asset, add an Execution entry pointing to UDamageExecCalc. No modifiers needed on the GE itself — the ExecCalc handles everything.
Conditional Gameplay Effects¶
The FGameplayEffectExecutionDefinition supports ConditionalGameplayEffects — additional GEs that will be applied if the execution succeeds and you call MarkConditionalGameplayEffectsToTrigger() on the output. Each conditional effect can have source tag requirements.
This is useful for "on hit" effects: if the damage calc succeeds, also apply a burn DoT.
Passed-In Tags¶
The execution definition has a PassedInTags container. These tags are made available inside the ExecCalc via ExecutionParams.GetPassedInTags(). Enable bRequiresPassedInTags on your ExecCalc class to use them. This provides a lightweight way for GE designers to pass context to the calculation without SetByCaller.
Tips¶
ExecCalcs Only Run on Authority
Execution Calculations only run on the server (or in standalone). They don't run on predicted clients. If you need client-side prediction for something an ExecCalc does, you'll need a different approach (like predicting the entire GE application with modifiers).
Keep It Testable
Extract your damage formula into a pure static function and call it from the ExecCalc. This makes it easy to write unit tests for the formula itself without needing a full GAS setup.
Output Modifier Operations
Output modifiers from ExecCalcs are typically Additive (adding/subtracting from an attribute). But you can use any EGameplayModOp. Override is particularly useful when you want to set an attribute to a specific value.