MoveTo task: How to move to player's location at any given location they are at

From what I’m noticing in my behavior tree, the moveTo task moves to a vector location given to it; it either makes it to its destination or terminates from stopMovement node. However, an issue has come up that I’m trying to resolve is to how to make moveTo move to the player’s location even when they move away from the location given from the task.

Ex. The player is standing still; the AI gets the player location and moves towards it. Okay.

However, this conjecture. The player is standing still. AI gets the player location and moves towards it. The player during that time frame, moves away from that location the AI is moving towards.

The latter scenario the AI Pawn will not calculate the Player’s location until the next iteration of the branch of the behavior tree. What I want is to get the Player’s location even when they move and customize moveTo to move to that location all the time.

I see a node like AIMoveTo, but I haven’t seen the effects of it within a behavior tree task yet. Questions I’ve seen using it fall under an AI’s character blueprint itself rather than the behavior tree it runs. Any advice appreciated. Thanks.

1 Like

If you’re using Behavior Tree task “Move To” all you have to do is to sent AI to an actor rather then actor’s location (a Vector). Just make sure the blackboard entry you’re sending your AI to is of type Object and points to an actor instance (player pawn is an actor).

Cheers,

–mieszko

Simple and fast! Will give it a shot. Thanks.

Hey Mieszko. Recently I had a situation where I needed to have path destination updated to reflect a vector in my blackboard, rather than the location of an actual actor (I had to calculate the destination point using an algorithm, from various parameters which included actor locations, and these parameters all changed with time). There are some workarounds of course, like using a proxy actor, or creating a new move request - though this seems to lead to the character stuttering as the new request takes over.

Anyway, it seems that the path observing based on an actor is built deep into the navigation system. It would be nice if this could be abstracted out to the level of observing a path based on something more generic - a delegate which provides the desired destination at a given time, for example. Not sure how big a job this would be though.

Not a big job actually, and were already thinking about implementing a way to go to “blackboard key” that would behave just like moving to actor in terms of observing the goal location. Making it super-generic-and-flexible has its merits, but on the other hand I’d like to keep things simple on the engine level.

Introducing powerful, flexible concepts that few people will use has to wait for better times, when AI in UE4 is a lot more robust. But thanks for the suggestion and don’t hesitate to throw more in our direction. We appreciate every single one of them!

@MieszkoZ but what if I want to create a custom behaviour tree task with “move to” and character references? It does not accept object references, casting to the character blueprint from the object pin is not working also.

cast or interface should work, are you sure the object is correct and valid?

It is actually a child blueprint, can’t I cast to the blueprint that is the parent of all my characters blueprints? I usually always cast to the parent blueprint and it had worked until now in that case

yeah again should, throw some print strings to see whats failing, is the object invalid, is the cast failing or is it sending but nothing is happening

Good idea! The print string showed it was actually detecting another actor, which is an animal, so I added tags and it’s working fine now, I’ll soon be ready to make 2 teams now that I have it fixxed. Thank you for the help!

Hope following codes can help somebody

MoveActorToLocationAndWait.h

#pragma once

#include "CoreMinimal.h"
#include "GameplayTask.h"
#include "Navigation/PathFollowingComponent.h"
#include "AbilitySystemComponent.h"
#include "MoveActorToLocationAndWait.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMoveActorToLocationDelegate);

UCLASS()
class CRISISINKARNO_API UMoveActorToLocationAndWait : public UGameplayTask
{
	GENERATED_BODY()
	
public:
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "TRUE"))
	static UMoveActorToLocationAndWait*  moveActorToLocationAndWait(UAbilitySystemComponent* abilitySystemComponent, AController* controller,
		const FVector& goalLocation, float AgentRadiusMultiplier = 1, float AgentHalfHeightMultiplier = 1.1);

	virtual void Activate() override;
	virtual void ExternalCancel() override;
	virtual void OnDestroy(bool AbilityEnded) override;

private:
	virtual void OnRequestFinished(FAIRequestID RequestID, const FPathFollowingResult& Result);
	UPathFollowingComponent* initNavigationController(AController& Controller);

public:
	UPROPERTY(BlueprintAssignable)
	FMoveActorToLocationDelegate  onCompleted;

	UPROPERTY(BlueprintAssignable)
	FMoveActorToLocationDelegate  onFailed;

private:
	AController* _controller = nullptr;
	FVector _goalLocation;
	float _agentRadiusMultiplier;
	float _agentHalfHeightMultiplier;
	UPathFollowingComponent* _pathFollowingComponent = nullptr;
	FDelegateHandle _callbackHandle;
};

MoveActorToLocationAndWait.cpp

#include "MoveActorToLocationAndWait.h"
#include "NavigationPath.h"
#include "NavigationData.h"
#include "NavigationSystem.h"
#include "NavMesh/NavMeshPath.h"
#include "Navigation/PathFollowingComponent.h"
#include "AIController.h"

UMoveActorToLocationAndWait* UMoveActorToLocationAndWait::moveActorToLocationAndWait(UAbilitySystemComponent* abilitySystemComponent, AController* controller, const FVector& goalLocation,
	float AgentRadiusMultiplier, float AgentHalfHeightMultiplier)
{
	UMoveActorToLocationAndWait* ret = NewObject<UMoveActorToLocationAndWait>();
	ret->_controller = controller;
	ret->_goalLocation = goalLocation;
	ret->_agentHalfHeightMultiplier = AgentHalfHeightMultiplier;
	ret->_agentRadiusMultiplier = AgentRadiusMultiplier;
	ret->InitTask(*abilitySystemComponent, abilitySystemComponent->GetGameplayTaskDefaultPriority());
	return ret;
}

UPathFollowingComponent* UMoveActorToLocationAndWait::initNavigationController(AController& Controller)
{
	AAIController* AsAIController = Cast<AAIController>(&Controller);
	UPathFollowingComponent* PathFollowingComp = nullptr;

	if (AsAIController)
	{
		PathFollowingComp = AsAIController->GetPathFollowingComponent();
	}
	else
	{
		PathFollowingComp = Controller.FindComponentByClass<UPathFollowingComponent>();
		if (PathFollowingComp == nullptr)
		{
			PathFollowingComp = NewObject<UPathFollowingComponent>(&Controller);
			PathFollowingComp->RegisterComponentWithWorld(Controller.GetWorld());
			PathFollowingComp->Initialize();
		}
	}
	
	return PathFollowingComp;
}

void UMoveActorToLocationAndWait::Activate()
{
	UNavigationSystemV1* NavSys = _controller ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(_controller->GetWorld()) : nullptr;
	if (NavSys == nullptr || _controller == nullptr || _controller->GetPawn() == nullptr)
	{
		EndTask();
		onFailed.Broadcast();
		return;
	}

	if (_pathFollowingComponent == nullptr)
	{
		_pathFollowingComponent = initNavigationController(*_controller);
		if (_pathFollowingComponent == nullptr)
		{
			EndTask();
			onFailed.Broadcast();
			return;
		}
	}

	if (!_pathFollowingComponent->IsPathFollowingAllowed())
	{
		EndTask();
		onFailed.Broadcast();
		return;
	}

	const bool bAlreadyAtGoal = _pathFollowingComponent->HasReached(_goalLocation, EPathFollowingReachMode::OverlapGoal);

	// script source, keep only one move request at time
	if (_pathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle)
	{
		_pathFollowingComponent->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest
			, FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep);
	}

	// script source, keep only one move request at time
	if (_pathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle)
	{
		_pathFollowingComponent->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest);
	}
	do
	{
		if (bAlreadyAtGoal)
		{
			_callbackHandle = _pathFollowingComponent->OnRequestFinished.AddUObject(this, &UMoveActorToLocationAndWait::OnRequestFinished);
			_pathFollowingComponent->RequestMoveWithImmediateFinish(EPathFollowingResult::Success);
		}
		else
		{
			const FVector AgentNavLocation = _controller->GetNavAgentLocation();
			const ANavigationData* NavData = NavSys->GetNavDataForProps(_controller->GetNavAgentPropertiesRef(), AgentNavLocation);
			if (!NavData)
				break;
			
			FPathFindingQuery Query(_controller, *NavData, AgentNavLocation, _goalLocation);
			FPathFindingResult Result = NavSys->FindPathSync(Query);
			if (Result.IsSuccessful())
			{
				//Result.Path->DebugDraw(NavData, FColor(255, 0, 0, 255), nullptr, true, 10, 0);	//debug
				_callbackHandle = _pathFollowingComponent->OnRequestFinished.AddUObject(this, &UMoveActorToLocationAndWait::OnRequestFinished);

				FAIMoveRequest requestData(_goalLocation);
				_pathFollowingComponent->SetPreciseReachThreshold(_agentRadiusMultiplier, _agentHalfHeightMultiplier);
				_pathFollowingComponent->RequestMove(requestData, Result.Path);
			}
			else if(_pathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle)
			{
				_callbackHandle = _pathFollowingComponent->OnRequestFinished.AddUObject(this, &UMoveActorToLocationAndWait::OnRequestFinished);
				_pathFollowingComponent->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid);
			}
			else {
				break;
			}
			
		}
		return;
	} while (false);

	EndTask();
	onFailed.Broadcast();
}

void UMoveActorToLocationAndWait::ExternalCancel()
{

}

void UMoveActorToLocationAndWait::OnDestroy(bool AbilityEnded)
{
	if (_pathFollowingComponent && _callbackHandle.IsValid())
		_pathFollowingComponent->OnRequestFinished.Remove(_callbackHandle);
	Super::OnDestroy(AbilityEnded);
}

void UMoveActorToLocationAndWait::OnRequestFinished(FAIRequestID requestID, const FPathFollowingResult& result)
{
	EndTask();
	onCompleted.Broadcast();
}