BTTask EBTNodeResult::InProgress

Has anyone had any experience in writing custom BTTasks?

I’m currently working on some flying AI and I’ve setup my aicontroller to handle flying movement, but I’m not sure how to handle moving from EBTNodeResult::InProgress to EBTNodeResult::Succeeded.

My current task code is below:

#include "MyGame.h"
#include "DragonController.h"
#include "BTTask_FlyTo.h"


UBTTask_FlyTo::UBTTask_FlyTo(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
	, arriveDistance(50.f)
{
	NodeName = "Fly To";
}

EBTNodeResult::Type UBTTask_FlyTo::ExecuteTask(UBehaviorTreeComponent* OwnerComp, uint8* NodeMemory)
{
	UBehaviorTreeComponent* MyComp = OwnerComp;
	FBTFlyToTaskMemory* MyMemory = (FBTFlyToTaskMemory*)NodeMemory;
	ADragonController* MyController = MyComp ? Cast<ADragonController>(MyComp->GetOwner()) : NULL;

	if (MyController == NULL)
	{
		return EBTNodeResult::Failed;
	}

	const UBlackboardComponent* MyBlackboard = OwnerComp->GetBlackboardComponent();

	if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Vector::StaticClass())
	{
		const FVector targetLocation = MyBlackboard->GetValueAsVector(BlackboardKey.GetSelectedKeyID());
		const FVector currentLocation = MyComp->GetOwner()->GetActorLocation();

		float distance = FVector::Dist(currentLocation, targetLocation);

		if (distance <= arriveDistance)
		{
			return EBTNodeResult::Succeeded;
		}

		//AI messaging stuff? I Assume
		const FAIRequestID RequestID = MyController->GetCurrentMoveRequestID();

		MyMemory->MoveRequestID = RequestID;
		WaitForMessage(OwnerComp, UBrainComponent::AIMessage_MoveFinished, RequestID);

		MyMemory->MoveRequestID = RequestID;
		MyController->FlyToTarget(targetLocation, arriveDistance);

		return EBTNodeResult::InProgress;
	}

	return EBTNodeResult::Failed;
}

And my Ai Controller function for flying is:

void ADragonController::Tick(float DeltaTime)
{
	UpdateMovement();
}

void ADragonController::FlyToTarget(FVector newTarget, float newArriveDistance)
{
	target = newTarget;
	flying = true;
	arriveDistance = newArriveDistance;
}

bool ADragonController::GetFlying()
{
	return flying;
}

void ADragonController::UpdateMovement()
{
	if (flying)
	{
		const FVector currentLocation = GetPawn()->GetActorLocation();
		const float distance = FVector::Dist(currentLocation, target);

		if (distance <= arriveDistance)
		{
			flying = false;
		}
		else
		{
			FVector direction = target - currentLocation;
			direction.Normalize();
			MoveInDirection(direction);
		}
	}
}

void ADragonController::MoveInDirection(const FVector moveDirection)
{
	ADragon* MyBot = Cast<ADragon>(GetPawn());

	if (MyBot)
	{
		MyBot->AddMovementInput(moveDirection, 10000.0f);
	}
}

Is there a way to fire off a message from the controller to indicate that the task has finished executing? The only way I can think right now would be providing the controller with a reference to the task and having the controller manually call OnFinishExecute.

Any help would be greatly appreciated.

I suggest using AI messages, just like built in path following does. You will need your own request Id to tie movement with behavior tree task (in case of any aborts and resumes in the same frame). If you plan to use both flying and regular walking by the same AI, use your unique message name, otherwise it can work with already existing one.

add to DragonController.h

private:
	static uint32 NextRequestId;
	FAIRequestID FlyRequestId;
	
	FORCEINLINE void StoreFlyRequestId() { FlyRequestId = NextRequestId++; }

public:
	FORCEINLINE FAIRequestID GetFlyRequestId() const { return FlyRequestId; }

Adjust task code to use your request Id:

// request movement
MyController->FlyToTarget(targetLocation, arriveDistance);

// wait for message to finish task
const FAIRequestID RequestID = MyController->GetFlyRequestId();

MyMemory->MoveRequestID = RequestID;
WaitForMessage(OwnerComp, UBrainComponent::AIMessage_MoveFinished, RequestID);

return EBTNodeResult::InProgress;

And finally, handle requests and send message when flying is finished (DragonController.cpp) :

void ADragonController::FlyToTarget(FVector newTarget, float newArriveDistance)
{
	// abort previous request
	if (flying)
	{
		FAIMessage Msg(UBrainComponent::AIMessage_MoveFinished, this, FlyRequestId, FAIMessage::Failure);
		FAIMessage::Send(this, Msg);
	}

    target = newTarget;
    flying = true;
    arriveDistance = newArriveDistance;

	// store new request
	StoreFlyRequestId();
}

void ADragonController::UpdateMovement()
...
       if (distance <= arriveDistance)
       {
         flying = false;

		 FAIMessage Msg(UBrainComponent::AIMessage_MoveFinished, this, FlyRequestId, FAIMessage::Success);
		 FAIMessage::Send(this, Msg);

		 FlyRequestId = FAIRequestID::InvalidRequest;
       }
1 Like

Absolute legend, thank you!

quick question, does the FAIMessage::Success changes the EBTNodeResult to Succeeded then?

that is correct. UBTTaskNode::ReceivedMessage default handler is finishing as Succeeded for FAIMessage::Success and as Failed for any other type.