Custom executor
Build a UMoveToLocationExecutor from empty file to working. The most common 'how do I extend the framework?' walkthrough.
[ Example ]
Custom executor — write a MoveTo action
The bundled executors (Wait, Move, PlayMontage, Notify…) cover the common cases. The next most-common question is: "how do I add my own action that does X?" This page walks through writing a UMoveToLocationExecutor from empty file to plugged-into-an-archetype. The same pattern applies to any custom executor: attack, fire weapon, talk to NPC, custom animation, Gameplay Event.
The contract
A UIntentActionExecutor subclass implements three lifecycle methods. The
executor reports completion by calling FinishAction(Result) where
Result ∈ {Succeeded, Failed, Aborted}.
| Method | Called by | When |
|---|---|---|
EnterAction(OwnerActor, Params) | Dispatcher | Once, when action starts |
TickAction(DeltaTime) | Dispatcher | Every frame while running |
ExitAction(Status) | Dispatcher | Once, on completion |
The header (UMoveToLocationExecutor.h)
#pragma once
#include "CoreMinimal.h"
#include "Action/IntentActionExecutor.h"
#include "UMoveToLocationExecutor.generated.h"
USTRUCT(BlueprintType)
struct FMoveToLocationParams
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite,
meta=(ToolTip="World location to move toward."))
FVector TargetLocation = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite,
meta=(ClampMin="1.0", ToolTip="How close is close enough (units)."))
float AcceptanceRadius = 50.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite,
meta=(ClampMin="0.0", ToolTip="Optional time-out before failing."))
float TimeoutSeconds = 0.f;
};
UCLASS(BlueprintType)
class MYPROJECT_API UMoveToLocationExecutor : public UIntentActionExecutor
{
GENERATED_BODY()
public:
virtual void EnterAction(AActor* OwnerActor, const FInstancedStruct& Params) override;
virtual void TickAction(float DeltaTime) override;
virtual void ExitAction(EIntentActionResult Status) override;
private:
FVector Target = FVector::ZeroVector;
float AcceptanceRadius = 50.f;
float Timeout = 0.f;
float Elapsed = 0.f;
TWeakObjectPtr<AAIController> AIController;
};The implementation (UMoveToLocationExecutor.cpp)
#include "UMoveToLocationExecutor.h"
#include "AIController.h"
#include "GameFramework/Pawn.h"
#include "Navigation/PathFollowingComponent.h"
void UMoveToLocationExecutor::EnterAction(AActor* OwnerActor, const FInstancedStruct& Params)
{
const FMoveToLocationParams& P = Params.Get<FMoveToLocationParams>();
Target = P.TargetLocation;
AcceptanceRadius = P.AcceptanceRadius;
Timeout = P.TimeoutSeconds;
Elapsed = 0.f;
if (APawn* Pawn = Cast<APawn>(OwnerActor))
{
AIController = Cast<AAIController>(Pawn->GetController());
if (AIController.IsValid())
{
AIController->MoveToLocation(Target, AcceptanceRadius);
}
}
}
void UMoveToLocationExecutor::TickAction(float DeltaTime)
{
Elapsed += DeltaTime;
if (!AIController.IsValid())
{
FinishAction(EIntentActionResult::Failed);
return;
}
if (Timeout > 0.f && Elapsed >= Timeout)
{
AIController->StopMovement();
FinishAction(EIntentActionResult::Failed);
return;
}
const EPathFollowingStatus::Type Status = AIController->GetMoveStatus();
if (Status == EPathFollowingStatus::Idle)
{
const FVector OwnerLoc = AIController->GetPawn()->GetActorLocation();
if (FVector::Dist(OwnerLoc, Target) <= AcceptanceRadius)
{
FinishAction(EIntentActionResult::Succeeded);
}
else
{
FinishAction(EIntentActionResult::Failed);
}
}
}
void UMoveToLocationExecutor::ExitAction(EIntentActionResult Status)
{
if (AIController.IsValid())
{
AIController->StopMovement();
}
}That's the entire executor. ~80 lines of C++.
Wiring it to an action asset
IntentAction data asset (e.g. DA_Action_MoveToFlank).UMoveToLocationExecutor in the class picker.FMoveToLocationParams.TargetLocation (probably set at runtime by goal-spawning logic; the default is a placeholder).OnFlank = true).The action is now usable by any archetype that includes it.
Common variations
Acceptance test
SpawnPawn with archetype using
DA_Action_MoveToFlankdropped in a level.PlanA goal whose plan includes MoveToFlank becomes active.
MovePawn pathfinds to
TargetLocationusing its AIController.ArrivedWithin AcceptanceRadius the Live Inspector shows the action completing with
Succeededand the next action (if any) starting.TimeoutIf Timeout elapses, the action completes with
Failedand the planner re-evaluates.