Ability Tasks¶
Ability Tasks (UAbilityTask) are the primary mechanism for doing asynchronous work inside a Gameplay Ability. They are small, self-contained operations that follow the pattern of "start something and wait until it finishes or is interrupted." Think of them as latent actions scoped to an ability's lifetime.
Need to play a montage and wait for it to finish? There is a task for that. Need to wait for a gameplay event? A task. Wait for an attribute to cross a threshold? Task. They are the bread and butter of ability implementation.
How Tasks Work¶
Lifecycle¶
Every ability task follows the same lifecycle:
- Construction — a static factory function creates and configures the task
- Activation —
ReadyForActivation()(orActivate()internally) starts the task - Running — the task does its work, potentially ticking each frame
- Callbacks — the task fires
BlueprintAssignabledelegates when things happen - Destruction —
OnDestroy()cleans up (unregisters delegates, etc.)
The critical rule: tasks are automatically cleaned up when their owning ability ends. You do not need to manually end tasks in your EndAbility override — the system handles it. However, you do need to handle the case where a task's callback fires and the ability should end.
The Factory Pattern¶
Tasks are never created with NewObject directly. Instead, each task class provides a static factory function (often called Create... or the task name itself):
UAbilityTask_PlayMontageAndWait* Task =
UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
this, // Owning ability
NAME_None, // Task instance name
MyMontage, // Parameters...
1.0f);
// Bind to output delegates
Task->OnCompleted.AddDynamic(this, &ThisClass::OnMontageCompleted);
Task->OnCancelled.AddDynamic(this, &ThisClass::OnMontageCancelled);
Task->OnInterrupted.AddDynamic(this, &ThisClass::OnMontageCancelled);
// IMPORTANT: Must call this to actually start the task!
Task->ReadyForActivation();
Don't forget ReadyForActivation
Creating a task does not start it. You must call ReadyForActivation() after binding your delegates. Forgetting this call is a common source of "my task does nothing" bugs.
ShouldBroadcastAbilityTaskDelegates¶
Before firing any callback, tasks check ShouldBroadcastAbilityTaskDelegates(). This returns false if the ability has ended or the ASC is no longer valid. This prevents callbacks from firing into a dead ability — a safety net, not something you typically need to worry about.
Instance Names¶
Tasks can be given instance names. This allows you to find and manipulate tasks by name from the ability:
ConfirmTaskByInstanceName(FName)— sends a confirm signal to matching tasksEndTaskByInstanceName(FName)— ends matching tasks (next frame)CancelTaskByInstanceName(FName)— cancels matching tasks (next frame)
This is useful for complex abilities with multiple concurrent tasks where you need fine-grained control.
Built-in Task Catalog¶
The engine ships with over 27 ability tasks. Here they are, organized by category.
Wait Tasks¶
These tasks wait for something to happen, then fire a delegate.
| Task | What It Does | Key Outputs |
|---|---|---|
| WaitDelay | Waits for a specified number of seconds | OnFinish |
| WaitCancel | Waits for the ability's cancel input (GenericCancel) | OnCancel |
| WaitConfirm | Waits for the ability's confirm input (GenericConfirm) | OnConfirm |
| WaitConfirmCancel | Combines confirm and cancel into one task | OnConfirm, OnCancel |
| WaitInputPress | Waits for the ability's bound input to be pressed | OnPress (receives time held) |
| WaitInputRelease | Waits for the ability's bound input to be released | OnRelease (receives time held) |
| WaitMovementModeChange | Waits for the character's movement mode to change | OnChange (receives new mode) |
| WaitOverlap | Waits for a primitive component overlap event | OnOverlap (receives overlap info) |
| WaitVelocityChange | Waits for the avatar's velocity to meet a threshold | OnVelocityChg |
| WaitAbilityActivate | Waits for another ability (matching tag query) to activate on this ASC | OnActivate (receives activated ability) |
| WaitAbilityCommit | Waits for another ability to commit | OnCommit (receives committed ability) |
Gameplay Effect Tasks¶
| Task | What It Does | Key Outputs |
|---|---|---|
| WaitGameplayEffectApplied_Self | Waits for a GE to be applied to the owner, with tag filtering | OnApplied (source actor, spec handle, active handle) |
| WaitGameplayEffectApplied_Target | Waits for a GE that the owner applied to others, with tag filtering | OnApplied (target actor, spec handle, active handle) |
| WaitGameplayEffectRemoved | Waits for a specific active GE (by handle) to be removed | OnRemoved, InvalidHandle |
| WaitGameplayEffectStackChange | Waits for a specific active GE's stack count to change | OnChange (old count, new count), InvalidHandle |
| WaitGameplayEffectBlockedImmunity | Waits for a GE to be blocked by immunity on the owner | Blocked (blocked spec, active immunity handle) |
Gameplay Tag Tasks¶
| Task | What It Does | Key Outputs |
|---|---|---|
| WaitGameplayTagAdded | Waits for a specific tag to be added to the owner (or external target) | Added |
| WaitGameplayTagRemoved | Waits for a specific tag to be removed from the owner (or external target) | Removed |
| WaitGameplayTagCountChanged | Fires when the count of a specific tag changes (added or removed) | TagCountChanged (new count) |
| WaitGameplayTagQuery | Fires when a FGameplayTagQuery transitions between true/false on the owner |
Triggered |
Tag tasks and external targets
The WaitGameplayTag tasks accept an OptionalExternalTarget actor, letting you watch tags on someone other than the owning actor.
Attribute Tasks¶
| Task | What It Does | Key Outputs |
|---|---|---|
| WaitAttributeChange | Fires when a specific attribute changes, with optional tag filtering | OnChange |
| WaitAttributeChangeThreshold | Fires when an attribute crosses a comparison threshold (>, <, ==, etc.) | OnChange |
| WaitAttributeChangeRatioThreshold | Fires when the ratio of two attributes crosses a threshold (e.g., Health/MaxHealth < 0.25) | OnChange |
WaitAttributeChange supports comparison operators via the WaitForAttributeChangeWithComparison factory:
auto* Task = UAbilityTask_WaitAttributeChange::WaitForAttributeChangeWithComparison(
this,
UMyAttributeSet::GetHealthAttribute(),
FGameplayTag(), // WithTag (optional source tag filter)
FGameplayTag(), // WithoutTag
EWaitAttributeChangeComparison::LessThan,
25.0f, // Comparison value
true); // Trigger once
Gameplay Event Tasks¶
| Task | What It Does | Key Outputs |
|---|---|---|
| WaitGameplayEvent | Waits for a gameplay event with a matching tag to be sent to the owner's ASC | EventReceived (FGameplayEventData payload) |
| WaitTargetData | Spawns or uses a Target Actor and waits for targeting data | ValidData (FGameplayAbilityTargetDataHandle), Cancelled |
WaitGameplayEvent is incredibly versatile. It is the primary mechanism for abilities that react to external events mid-execution (e.g., "wait for the hit event from the anim notify, then apply damage").
auto* Task = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
this,
FGameplayTag::RequestGameplayTag("Event.Montage.Hit"),
nullptr, // OptionalExternalTarget
false, // OnlyTriggerOnce — false to keep listening
true); // OnlyMatchExact
Task->EventReceived.AddDynamic(this, &ThisClass::OnHitEvent);
Task->ReadyForActivation();
Action Tasks¶
| Task | What It Does | Key Parameters |
|---|---|---|
| PlayMontageAndWait | Plays an anim montage and waits for completion, blend out, interruption, or cancellation | Montage, Rate, StartSection, bStopWhenAbilityEnds, StartTimeSeconds |
| PlayAnimAndWait | Simpler animation playback without montage features | AnimSequence |
| SpawnActor | Spawns an actor with deferred construction (supports ExposeOnSpawn) | ActorClass |
| MoveToLocation | Smoothly moves the avatar to a target location over time | TargetLocation, Duration, Curve |
| VisualizeTargeting | Visualizes targeting (spawns/updates a target actor each tick) | TargetActorClass, Duration |
| StartAbilityState | Creates a named state that can be ended externally | StateName, bEndCurrentState |
| NetworkSyncPoint | Waits for both client and server to reach the same point | OnSync, OnSyncFailed (enum: BothWait, OnlyServerWait, OnlyClientWait) |
| Repeat | Repeats a callback a specified number of times with a delay between each | TimeBetweenActions, TotalActionCount |
PlayMontageAndWait deserves special mention — it is by far the most commonly used task. Its delegates cover every outcome:
OnCompleted— montage played to the endOnBlendedIn— montage blend-in finished (5.7+)OnBlendOut— montage started blending outOnInterrupted— another montage overwrote this oneOnCancelled— the ability was cancelled
Root Motion Tasks¶
These tasks apply root motion forces to the character, useful for dashes, knockbacks, lunges, and similar movement abilities.
| Task | What It Does | Key Parameters |
|---|---|---|
| ApplyRootMotion_Base | Abstract base class for root motion tasks | Strength, Duration, bFinishOnMovementEnd |
| ApplyRootMotionConstantForce | Applies a constant directional force | WorldDirection, Strength, Duration, bIsAdditive |
| ApplyRootMotionJumpForce | Jump arc with separate horizontal and vertical control | Rotation, Distance, Height, Duration, Curve |
| ApplyRootMotionMoveToActorForce | Moves toward a target actor over time | TargetActor, StretchFactor, OffsetFromTarget |
| ApplyRootMotionMoveToForce | Moves toward a world location over time | TargetLocation, Duration, bSetNewMovementMode |
| ApplyRootMotionRadialForce | Applies a radial push/pull (explosion knockback, vortex pull) | Location, Radius, Strength |
All root motion tasks share common output delegates: OnFinish (completed normally) and typically a callback for when movement is blocked.
Writing Custom Tasks¶
When the built-in tasks do not cover your use case, writing a custom one is straightforward. Here is a template:
UCLASS()
class UAbilityTask_WaitForSomething : public UAbilityTask
{
GENERATED_BODY()
public:
// Output delegate — the "pin" that fires in Blueprint
UPROPERTY(BlueprintAssignable)
FMyTaskDelegate OnSomethingHappened;
// Static factory function
UFUNCTION(BlueprintCallable, Category = "Ability|Tasks",
meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility",
BlueprintInternalUseOnly = "TRUE"))
static UAbilityTask_WaitForSomething* WaitForSomething(
UGameplayAbility* OwningAbility,
FName TaskInstanceName,
float MyParameter);
virtual void Activate() override;
virtual void OnDestroy(bool bInOwnerFinished) override;
private:
float MyParameter;
FDelegateHandle MyCallbackHandle;
};
UAbilityTask_WaitForSomething* UAbilityTask_WaitForSomething::WaitForSomething(
UGameplayAbility* OwningAbility,
FName TaskInstanceName,
float MyParameter)
{
UAbilityTask_WaitForSomething* Task =
NewAbilityTask<UAbilityTask_WaitForSomething>(OwningAbility, TaskInstanceName);
Task->MyParameter = MyParameter;
return Task;
// NOTE: Do NOT call Activate or fire delegates here!
}
void UAbilityTask_WaitForSomething::Activate()
{
Super::Activate();
// Register for whatever event you're waiting for
// Example: subscribe to a delegate on a component
if (AActor* Avatar = GetAvatarActor())
{
// MyCallbackHandle = SomeComponent->OnSomething.AddUObject(...);
}
}
void UAbilityTask_WaitForSomething::OnDestroy(bool bInOwnerFinished)
{
// ALWAYS unregister your callbacks here
// if (MyCallbackHandle.IsValid()) { ... }
Super::OnDestroy(bInOwnerFinished);
}
Checklist for custom tasks
- Override
OnDestroyand unregister all callbacks — this is called when the ability ends - All setup logic goes in
Activate(), not the factory function - Never fire delegates from the factory function
- Check
ShouldBroadcastAbilityTaskDelegates()before broadcasting - Use
NewAbilityTask<T>()to create the instance (handles init and debug tracking)
Making Tasks Tick¶
If your task needs to do work every frame, override TickTask:
// In the header
virtual void TickTask(float DeltaTime) override;
// In Activate()
bTickingTask = true;
Set bTickingTask = true before or during Activate() to opt in. The task will tick as long as it is alive.
Task Lifetime and Auto-Cleanup¶
One of the best features of ability tasks is that they automatically clean up when their owning ability ends. You do not need to manually track and destroy tasks. The system calls OnDestroy on every active task when EndAbility fires.
This means:
- A montage task will stop the montage when the ability ends (if
bStopWhenAbilityEndsistrue) - Delegate registrations in
OnDestroyare cleaned up - You do not need to call
EndTask()on tasks from yourEndAbilityoverride
However, you can manually end a task early by calling EndTask() on it, or by using the instance name functions on the ability.
Tasks and Instancing Policy¶
Tasks require an instanced ability. They store a pointer to the owning UGameplayAbility instance and use it for delegate callbacks, lifecycle management, and ASC access.
- InstancedPerActor — tasks work perfectly
- InstancedPerExecution — tasks work perfectly
- NonInstanced — tasks do not work (the ability is a CDO, not a real instance)
See Instancing Policy for more details on why this matters.