Skip to content

Instantly share code, notes, and snippets.

@olddeda
Created February 25, 2023 12:32
Show Gist options
  • Select an option

  • Save olddeda/e2d816c316f664928b934f30498741ff to your computer and use it in GitHub Desktop.

Select an option

Save olddeda/e2d816c316f664928b934f30498741ff to your computer and use it in GitHub Desktop.
#include "AsyncMoveTo.h"
#include "AIController.h"
#include "AISystem.h"
#include "NavigationSystem.h"
#include "Logging/MessageLog.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AsyncMoveTo)
DEFINE_LOG_CATEGORY_STATIC(AsyncMoveTo, Warning, All);
#define LOCTEXT_NAMESPACE "AsyncMoveTo"
namespace
{
UPathFollowingComponent* 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;
}
}
UAsyncMoveTo::UAsyncMoveTo(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
AddToRoot();
}
MoveRequestID = FAIRequestID::InvalidRequest;
MoveRequest.SetAcceptanceRadius(GET_AI_CONFIG_VAR(AcceptanceRadius));
MoveRequest.SetReachTestIncludesAgentRadius(GET_AI_CONFIG_VAR(bFinishMoveOnGoalOverlap));
}
UAsyncMoveTo* UAsyncMoveTo::AsyncMoveToLocationOrActor(AController* Controller, FVector InGoalLocation, AActor* InGoalActor, float AcceptanceRadius, bool StopOnOverlap)
{
UAsyncMoveTo* Task = NewObject<UAsyncMoveTo>();
Task->SetUp(Controller, InGoalLocation, InGoalActor, AcceptanceRadius, StopOnOverlap);
return Task;
}
UAsyncMoveTo* UAsyncMoveTo::AsyncMoveToActor(AController* Controller, AActor* InGoalActor)
{
UAsyncMoveTo* Task = NewObject<UAsyncMoveTo>();
Task->SetUp(Controller, FVector::Zero(), InGoalActor);
return Task;
}
UAsyncMoveTo* UAsyncMoveTo::AsyncMoveToLocation(AController* Controller, FVector InGoalLocation)
{
UAsyncMoveTo* Task = NewObject<UAsyncMoveTo>();
Task->SetUp(Controller, InGoalLocation, nullptr);
return Task;
}
void UAsyncMoveTo::SetUp(AController* InController, FVector InGoalLocation, AActor* InGoalActor, float AcceptanceRadius, bool StopOnOverlap)
{
UE_LOG(AsyncMoveTo, Warning, TEXT("SetUp"));
Controller = InController;
GoalLocation = InGoalLocation;
GoalActor = InGoalActor;
if (InGoalActor)
{
MoveRequest.SetGoalActor(InGoalActor);
}
else
{
MoveRequest.SetGoalLocation(InGoalLocation);
}
MoveRequest.SetAcceptanceRadius(AcceptanceRadius);
MoveRequest.SetReachTestIncludesAgentRadius(StopOnOverlap);
NavSys = Controller ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(Controller->GetWorld()) : nullptr;
if (NavSys == nullptr || Controller == nullptr || Controller->GetPawn() == nullptr)
{
UE_LOG(LogNavigation, Warning, TEXT("UNavigationSystemV1::AsyncMoveTo called for NavSys:%s Controller:%s controlling Pawn:%s (if any of these is None then there's your problem"),
*GetNameSafe(NavSys), *GetNameSafe(Controller), Controller ? *GetNameSafe(Controller->GetPawn()) : TEXT("NULL"));
return;
}
PathFollowing = InitNavigationController(*Controller);
if (PathFollowing == nullptr)
{
FMessageLog("PIE").Warning(FText::Format(
LOCTEXT("AsyncMoveToErrorNoComp", "AsyncMoveTo failed for {0}: missing components"),
FText::FromName(Controller->GetFName())
));
return;
}
if (!PathFollowing->IsPathFollowingAllowed())
{
FMessageLog("PIE").Warning(FText::Format(
LOCTEXT("AsyncMoveToErrorMovement", "AsyncMoveTo failed for {0}: movement not allowed"),
FText::FromName(Controller->GetFName())
));
return;
}
Run();
}
void UAsyncMoveTo::Run()
{
const bool bAlreadyAtGoal = (GoalActor != nullptr) ? PathFollowing->HasReached(*GoalActor, EPathFollowingReachMode::OverlapAgent) : PathFollowing->HasReached(GoalLocation, EPathFollowingReachMode::OverlapAgent);
if (PathFollowing->GetStatus() != EPathFollowingStatus::Idle)
{
PathFollowing->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest, FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep);
}
if (PathFollowing->GetStatus() != EPathFollowingStatus::Idle)
{
PathFollowing->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest);
}
if (bAlreadyAtGoal)
{
PathFollowing->RequestMoveWithImmediateFinish(EPathFollowingResult::Success);
OnSuccess.Broadcast(EPathFollowingResult::Success);
return;
}
const FVector AgentNavLocation = Controller->GetNavAgentLocation();
const ANavigationData* NavData = NavSys->GetNavDataForProps(Controller->GetNavAgentPropertiesRef(), AgentNavLocation);
if (NavData)
{
FPathFindingQuery Query(Controller, *NavData, AgentNavLocation, ((GoalActor != nullptr) ? GoalActor->GetActorLocation() : GoalLocation));
FPathFindingResult Result = NavSys->FindPathSync(Query);
if (Result.IsSuccessful())
{
if (GoalActor != nullptr)
{
Result.Path->SetGoalActorObservation(*GoalActor, 100.0f);
}
MoveRequestID = PathFollowing->RequestMove(MoveRequest, Result.Path);
PathFollowing->OnRequestFinished.AddUObject(this, &UAsyncMoveTo::OnRequestFinished);
}
else if (PathFollowing->GetStatus() != EPathFollowingStatus::Idle)
{
PathFollowing->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid);
OnFail.Broadcast(EPathFollowingResult::Invalid);
}
}
}
void UAsyncMoveTo::OnRequestFinished(FAIRequestID RequestID, const FPathFollowingResult& Result)
{
UE_LOG(AsyncMoveTo, Warning, TEXT("OnRequestFinished"));
PathFollowing->OnRequestFinished.RemoveAll(this);
if (RequestID == MoveRequestID)
{
if (Result.IsSuccess())
{
OnSuccess.Broadcast(Result.Code);
}
else
{
OnFail.Broadcast(Result.Code);
}
}
RemoveFromRoot();
}
#undef LOCTEXT_NAMESPACE
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Engine/EngineTypes.h"
#include "AITypes.h"
#include "Navigation/PathFollowingComponent.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "AsyncMoveTo.generated.h"
class UNavigationSystemV1;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAsyncMoveToDelegate, TEnumAsByte<EPathFollowingResult::Type>, MovementResult);
UCLASS()
class OLDTALE_API UAsyncMoveTo : public UBlueprintAsyncActionBase
{
GENERATED_UCLASS_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "AI|Navigation", meta = (AdvancedDisplay = "AcceptanceRadius,StopOnOverlap", DefaultToSelf = "Controller", BlueprintInternalUseOnly = "TRUE", DisplayName = "Async Move To"))
static UAsyncMoveTo* AsyncMoveToLocationOrActor(AController* Controller, FVector Destination, AActor* TargetActor = nullptr, float AcceptanceRadius = 5.f, bool StopOnOverlap = false);
UFUNCTION(BlueprintCallable, Category = "AI|Navigation", meta = (DefaultToSelf = "Controller", BlueprintInternalUseOnly = "TRUE", DisplayName = "Async Move To Actor"))
static UAsyncMoveTo* AsyncMoveToActor(AController* Controller, AActor* TargetActor);
UFUNCTION(BlueprintCallable, Category = "AI|Navigation", meta = (DefaultToSelf = "Controller", BlueprintInternalUseOnly = "TRUE", DisplayName = "Async Move To Location"))
static UAsyncMoveTo* AsyncMoveToLocation(AController* Controller, FVector Destination);
public:
UPROPERTY(BlueprintAssignable)
FAsyncMoveToDelegate OnSuccess;
UPROPERTY(BlueprintAssignable)
FAsyncMoveToDelegate OnFail;
private:
void SetUp(AController* Controller, FVector Destination, AActor* TargetActor = nullptr, float AcceptanceRadius = 5.f, bool StopOnOverlap = false);
void Run();
protected:
UPROPERTY()
FAIMoveRequest MoveRequest;
FAIRequestID MoveRequestID;
AController* Controller;
FVector GoalLocation;
AActor* GoalActor;
UNavigationSystemV1* NavSys;
UPathFollowingComponent* PathFollowing;
virtual void OnRequestFinished(FAIRequestID RequestID, const FPathFollowingResult& Result);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment