Skip to content

Programming Guide

Michael Adaixo edited this page Feb 1, 2024 · 14 revisions

FlowPilot Task Node

This page will guide you on how to create a new FPTaskNode in Cpp and in Blueprint.

Lets begin by understanding what's a node, which methods it's running and what you must do to properly implement them.

Anatomy of a TaskNode

Each TaskNode has a Setup() method. This is called at the beginning of the FlowPilotComponent execution, once, per-tasknode! This is important. This method is where you'd execute code only once (i.e. Prefetching a set of actors)

FlowPilotComponent will then start by entering the first TaskNode by calling its Enter() method. Enter() returns true or false whether it succeeded entering the node. Upon success, Tick() is called.

This is where most of the logic happens (although you can do that in Enter() if you don't need to run Tick().). Here, returning EFPTaskNodeResult::Succeed or others will either complete, continue or fail/error the execution.

When a node returns Succeed, its Exit() method is called, and we Enter() the next node in the sequence.

FlowPilot Node Implementable Methods

See FlowPilot Architecture page for lifecycle reference.

UFPTaskNode Interface

	// UFPTaskNode
	// Setups Node. Called once per FlowPilotExecution, even after restarts.
	virtual void Setup(FFlowContext* InContext);
	// Called when starting this Node. Returns true on success
	virtual bool Enter();
	// Called on Tick. Will success automatically if not implemented by Child classes
	virtual EFPNodeResult Tick(float DeltaTime);
	// Called when Tick returns Succeeds
	virtual void Exit();
	// Resets all nodes into their Setup States
	virtual void Reset();

	// !! Implement if Task has Child TaskNodes !!
	// Returns true if has ChildNodes
	virtual bool HasChildNodes() const { return false; }
	// Returns the list of ChildNodes
	virtual void GetChildNodes(TArray<TObjectPtr<UFPTaskNode>>& OutChildNodes) PURE_VIRTUAL(,)
	// Returns the number of ChildNodes
	virtual uint32 GetNumChildNodes() const { return 0; }
	
#if WITH_EDITOR
	// Returns true if valid. Child nodes should implement their Validations
	virtual bool IsNodeDataValid(FDataValidationContext& InContext) { return true; }
#endif

#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST
    // Gathers information to display to debug view about node.
	virtual void GetRuntimeDescription(TArray<FString>& OutLines) const {};
#endif
	//~UFPTaskNode

Creating a new FPTaskNode in Code

As an example, lets imagine you've created a Chest in your game. This chest can be opened and you can grab its contents.

We'll create a FPTaskNode called OpenChest

UCLASS(DisplayName="Interaction | Open Chest")
class MYGAME_API UFPTask_OpenChest : public UFPTaskNode
{
	GENERATED_BODY()
	
public:
	UFPTask_OpenChest();
	
	virtual void Setup(FFlowContext* InContext) override;
	virtual bool Enter() override;

protected:
	// Actor interacting with the Chest
	UPROPERTY(EditAnywhere, Category = "Flow Pilot")
	FFlowActorReference ActorReference;
	
	// Chest Actor Reference
	UPROPERTY(EditAnywhere, Category = "Flow Pilot")
	FFlowActorReference ChestActorReference;
};

We're just implementing Setup() and Enter() here. Setup() because we'll want to prefetch/cache the Actor References and Enter() only because we can execute all our code in there and exit without running long operations in Tick().

void UFPTask_OpenChest::Setup(FFlowContext* InContext)
{
    Super::Setup(InContext);
    PrefetchActor(ActorReference);
    PrefetchActor(ChestActorReference);
}

bool UFPTask_OpenChest::Enter()
{
    AActor* Actor = FindActor(ActorReference);
    if (!IsValid(Actor))
    {
        return false;
    }
    
    AActor* ChestActor = FindActor(ChestActorReference);
    if (!IsValid(ChestActor))
    {
        return false;
    }

    AChest* ChestActor = Cast<AChest>(ChestActor);
    if(!IsValid(ChestActor))
    {
        return false;
    }
    
    // Calling Functionality of the Chest, and providing the Actor who interacted with it.
    // Assumes OpenChest returns True on Success.
    return ChestActor->OpenChest(Actor);
}

Hopefully this simple example allows you to understand how we can create simple TaskNodes and re-use them.

Creating a new FPTaskNode in Blueprint

  1. Create new Blueprint and Select FPTask_BlueprintBase as the Parent Class.

image

  1. Implement needed virtual methods with implementation details.

image

  1. Add needed variables i.e. FFlowActorReference

image

Sample Implementation of Turning On/Off LightComponents on a List (TArray) of ActorReferences. image

4.1 (Optional) Implement Tick Method. ! If you implement tick, make sure you call the Return Node with Succeeded or Failed otherwise execution doesn't stop (unless this is aborted by another Task. i.e. Parallel Node).

image

  1. Ready to use.

image

Clone this wiki locally