BTTask_MoveToward - Asynchronous "Move To" Behavior Tree task

Out of the box, UE4 provides a MoveTo task that moves the character toward a location or actor. However, that node doesn’t return until the character has reached the specified location. This causes the behavior tree to freeze until it reaches the final location. This can cause jerky movement.

Almost every Behavior Tree tutorial I’ve seen seems to have to either work around this limitation, or just writes their own move to task. It seems silly for everybody to keep writing the same logic over and over, so I created a new task that works exactly like the built-in MoveTo task, only it returns immediately. Essentially, it makes the behavior asynchronous, since the tree continues execution immediately, which is the behavior people (and by “people”, I mean “me” :D) seem to need more often.

Here is the code. Feel free to use it, modify it, or do anything you want to it without limitation. Improvements and comments welcome.

You will have to make two changes to use this in your project. You’ll have to add your project-specific API macro in the header file, and your project-specific project header file in the implementation file. Just look for the brackets and copy the values from another class in your project.

BTW: Is there any way to make UE4 classes like this generic so they can just be dropped in to a project?

BTTask_MoveToward.h



#pragma once

#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BTTask_MoveToward.generated.h"

/**
 * 
 */
UCLASS(config=Game)
class [YOURPROJECT_API_MACRO] UBTTask_MoveToward : public UBTTask_BlackboardBase
{
	GENERATED_UCLASS_BODY()
    
    UPROPERTY(config, Category=Node, EditAnywhere, meta=(ClampMin = "0.0"))
    float AcceptableRadius;

    UPROPERTY(Category=Node, EditAnywhere)
    uint32 bAllowStrafe : 1;
    
    UPROPERTY(Category=Node, EditAnywhere)
    TSubclassOf<class UNavigationQueryFilter> FilterClass;
    

    virtual EBTNodeResult::Type ExecuteTask(class UBehaviorTreeComponent* OwnerComp, uint8* NodeMemory) override;
    virtual FString GetStaticDescription() const override;
    
#if WITH_EDITOR
    virtual FName GetNodeIconName() const override;
#endif // WITH_EDITOR
};


BTTask_MoveToward.cpp



#include "[Your Project Include].h"
#include "BTTask_MoveToward.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Vector.h"
#include "AIController.h"

// This class is identical to UBTTask_MoveTo except that it returns immediately rather than waiting until the character has reached the specified destination
//     This allows the behavior tree to continue executing and lets the character move smoothly without using a Simple Parallel node

UBTTask_MoveToward::UBTTask_MoveToward(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
    , AcceptableRadius(50.f)
    , bAllowStrafe(false)
{
    NodeName = "Move Toward";
    BlackboardKey.AddObjectFilter(this, AActor::StaticClass());
    BlackboardKey.AddVectorFilter(this);
}

EBTNodeResult::Type UBTTask_MoveToward::ExecuteTask(class UBehaviorTreeComponent* OwnerComp, uint8* NodeMemory)
{
    AAIController* MyController = OwnerComp ? Cast<AAIController>(OwnerComp->GetOwner()) : NULL;
    const UBlackboardComponent* MyBlackboard = OwnerComp->GetBlackboardComponent();

    EPathFollowingRequestResult::Type RequestResult = EPathFollowingRequestResult::Failed;

    if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Object::StaticClass())
    {
        UObject* KeyValue = MyBlackboard->GetValueAsObject(BlackboardKey.GetSelectedKeyID());
        AActor* TargetActor = Cast<AActor>(KeyValue);
        if (TargetActor)
        {
            RequestResult = MyController->MoveToActor(TargetActor, AcceptableRadius, true, true, bAllowStrafe, FilterClass);
        }
    }
    else if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Vector::StaticClass())
    {
        const FVector TargetLocation = MyBlackboard->GetValueAsVector(BlackboardKey.GetSelectedKeyID());
        RequestResult = MyController->MoveToLocation(TargetLocation, AcceptableRadius, true, true, false, bAllowStrafe, FilterClass);
    }


    if (RequestResult == EPathFollowingRequestResult::Failed)
    {
        return EBTNodeResult::Failed;
    }

    return EBTNodeResult::Succeeded;

}

FString UBTTask_MoveToward::GetStaticDescription() const
{
    return "Moves the character toward the specified point, returning immediately.";
}

#if WITH_EDITOR

FName UBTTask_MoveToward::GetNodeIconName() const
{
    return FName("BTEditor.Graph.BTNode.Task.MoveTo.Icon");
}

#endif	// WITH_EDITOR

3 Likes

You just saved me, I was looking at exactly this. Thank you so much! No idea why UE does not provide it out of the box as it seems to be so indispensable