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

As a simple example, we'll create a TaskNode that Toggles Actors Visibility in Game.

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

image

  1. We need to add the parameters needed to make it generic, so we can re-use the TaskNode. I'll want an Actor Reference.

image

  1. I'll add a Boolean to Toggle Visibility On or Off

image

  1. I'm implementing the Setup method, so I can Prefetch the Actor References.

image

  1. I'll now implement the Enter() method.

I'll start by calling Find All Actors of that Reference. The way I'm planning to use this node is to fetch all Actors that have the same GameplayTag. This method returns an array of all Actors it could find.

image

Next step is looping through the Actor pointers in the array and Setting their "Hidden In Game" boolean value. Without forgeting to call the Return Nodes.

image

  1. We can now use the Node in our FlowPilot Asset

image