class UKismetAIAsyncTaskProxy* UKismetAIHelperLibrary::CreateMoveToProxyObject(class UObject* WorldContextObject, APawn* Pawn, FVector Destination, AActor* TargetActor, float AcceptanceRadius, bool bStopOnOverlap) { check(WorldContextObject); if (!Pawn) { return NULL; } UKismetAIAsyncTaskProxy* MyObj = NULL; AAIController* AIController = Cast<AAIController>(Pawn->GetController()); if (AIController) { MyObj = NewObject<UKismetAIAsyncTaskProxy>(); FNavPathSharedPtr Path = TargetActor ? AIController->FindPath(TargetActor, true) : AIController->FindPath(Destination, true); if (Path.IsValid()) { MyObj->AIController = AIController; MyObj->AIController->ReceiveMoveCompleted.AddDynamic(MyObj, &UKismetAIAsyncTaskProxy::OnMoveCompleted); MyObj->MoveRequestId = MyObj->AIController->RequestMove(Path, TargetActor, AcceptanceRadius, bStopOnOverlap); } else { UWorld* World = GEngine->GetWorldFromContextObject( WorldContextObject ); World->GetTimerManager().SetTimer(MyObj, &UKismetAIAsyncTaskProxy::OnNoPath, 0.1, false); } } return MyObj; }
void UNavigationComponent::AsyncGeneratePath_OnCompleteCallback(uint32 QueryID, ENavigationQueryResult::Type Result, FNavPathSharedPtr ResultPath) { if (QueryID != uint32(AsynPathQueryID)) { UE_VLOG(GetOwner(), LogNavigation, Display, TEXT("Pathfinding query %u finished, but it's different from AsynPathQueryID (%d)"), QueryID, AsynPathQueryID); return; } check(GetOuter() != NULL); if (Result == ENavigationQueryResult::Success && ResultPath.IsValid() && ResultPath->IsValid()) { const bool bIsSamePath = Path.IsValid() == true && ((const FNavMeshPath*)Path.Get())->ContainsWithSameEnd((const FNavMeshPath*)ResultPath.Get()) && FVector::DistSquared(Path->GetDestinationLocation(), ResultPath->GetDestinationLocation()) < KINDA_SMALL_NUMBER; if (bIsSamePath == false) { if (IsFollowing() == true) { // make sure ticking is enabled (and it shouldn't be enabled before _first_ async path finding) SetComponentTickEnabledAsync(true); } SetPath(ResultPath); CurrentGoal = ResultPath->PathPoints[ ResultPath->PathPoints.Num() - 1 ].Location; NotifyPathUpdate(); } else { UE_VLOG(GetOwner(), LogNavigation, Display, TEXT("Skipping newly generated path due to it being the same as current one")); } } else { ResetTransientData(); if (MyPathObserver != NULL) { MyPathObserver->OnPathFailed(this); } } }
// DEPRECATED FUNCTION SUPPORT FAIRequestID AAIController::RequestPathAndMove(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query) { FAIRequestID MoveId = FAIRequestID::InvalidRequest; FNavPathSharedPtr FoundPath; FindPathForMoveRequest(MoveRequest, Query, FoundPath); if (FoundPath.IsValid()) { MoveId = RequestMove(MoveRequest, FoundPath); } return MoveId; }
EPathFollowingRequestResult::Type AAIController::MoveToActor(class AActor* Goal, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bCanStrafe, TSubclassOf<UNavigationQueryFilter> FilterClass) { SCOPE_CYCLE_COUNTER(STAT_MoveToActor); EPathFollowingRequestResult::Type Result = EPathFollowingRequestResult::Failed; UE_VLOG(this, LogNavigation, Log, TEXT("MoveToActor: Goal(%s) AcceptRadius(%.1f%s) bUsePathfinding(%d) bCanStrafe(%d)"), *GetNameSafe(Goal), AcceptanceRadius, bStopOnOverlap ? TEXT(" + agent") : TEXT(""), bUsePathfinding, bCanStrafe); if (Goal) { if (PathFollowingComponent && PathFollowingComponent->HasReached(Goal, AcceptanceRadius, !bStopOnOverlap)) { UE_VLOG(this, LogNavigation, Log, TEXT("MoveToActor: already at goal!")); PathFollowingComponent->SetLastMoveAtGoal(true); OnMoveCompleted(0, EPathFollowingResult::Success); Result = EPathFollowingRequestResult::AlreadyAtGoal; } else { FNavPathSharedPtr Path = FindPath(Goal, bUsePathfinding, UNavigationQueryFilter::GetQueryFilter(FilterClass)); if (Path.IsValid()) { bAllowStrafe = bCanStrafe; const uint32 RequestID = RequestMove(Path, Goal, AcceptanceRadius, bStopOnOverlap); Result = (RequestID != INVALID_MOVEREQUESTID) ? EPathFollowingRequestResult::RequestSuccessful : EPathFollowingRequestResult::Failed; } } } if (Result == EPathFollowingRequestResult::Failed) { if (PathFollowingComponent) { PathFollowingComponent->SetLastMoveAtGoal(false); } OnMoveCompleted(INVALID_MOVEREQUESTID, EPathFollowingResult::Invalid); } return Result; }
EPathFollowingRequestResult::Type AAIController::MoveToLocation(const FVector& Dest, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf<UNavigationQueryFilter> FilterClass) { SCOPE_CYCLE_COUNTER(STAT_MoveToLocation); EPathFollowingRequestResult::Type Result = EPathFollowingRequestResult::Failed; bool bCanRequestMove = true; UE_VLOG(this, LogNavigation, Log, TEXT("MoveToLocation: Goal(%s) AcceptRadius(%.1f%s) bUsePathfinding(%d) bCanStrafe(%d)"), *Dest.ToString(), AcceptanceRadius, bStopOnOverlap ? TEXT(" + agent") : TEXT(""), bUsePathfinding, bCanStrafe); // Check input is valid if( Dest.ContainsNaN() ) { UE_VLOG(this, LogNavigation, Error, TEXT("AAIController::MoveToLocation: Destination is not valid! Goal(%s) AcceptRadius(%.1f%s) bUsePathfinding(%d) bCanStrafe(%d)"), *Dest.ToString(), AcceptanceRadius, bStopOnOverlap ? TEXT(" + agent") : TEXT(""), bUsePathfinding, bCanStrafe); ensure( !Dest.ContainsNaN() ); bCanRequestMove = false; } FVector GoalLocation = Dest; // fail if projection to navigation is required but it either failed or there's not navigation component if (bCanRequestMove && bProjectDestinationToNavigation && (NavComponent == NULL || !NavComponent->ProjectPointToNavigation(Dest, GoalLocation))) { UE_VLOG_LOCATION(this, Dest, 30.f, FLinearColor::Red, TEXT("AAIController::MoveToLocation failed to project destination location to navmesh")); bCanRequestMove = false; } if (bCanRequestMove && PathFollowingComponent && PathFollowingComponent->HasReached(GoalLocation, AcceptanceRadius, !bStopOnOverlap)) { UE_VLOG(this, LogNavigation, Log, TEXT("MoveToLocation: already at goal!")); PathFollowingComponent->SetLastMoveAtGoal(true); OnMoveCompleted(0, EPathFollowingResult::Success); Result = EPathFollowingRequestResult::AlreadyAtGoal; bCanRequestMove = false; } if (bCanRequestMove) { FNavPathSharedPtr Path = FindPath(GoalLocation, bUsePathfinding, UNavigationQueryFilter::GetQueryFilter(FilterClass)); if (Path.IsValid()) { bAllowStrafe = bCanStrafe; const uint32 RequestID = RequestMove(Path, NULL, AcceptanceRadius, bStopOnOverlap); Result = (RequestID != INVALID_MOVEREQUESTID) ? EPathFollowingRequestResult::RequestSuccessful : EPathFollowingRequestResult::Failed; } } if (Result == EPathFollowingRequestResult::Failed) { if (PathFollowingComponent) { PathFollowingComponent->SetLastMoveAtGoal(false); } OnMoveCompleted(INVALID_MOVEREQUESTID, EPathFollowingResult::Invalid); } return Result; }
FPathFollowingRequestResult AAIController::MoveTo(const FAIMoveRequest& MoveRequest, FNavPathSharedPtr* OutPath) { // both MoveToActor and MoveToLocation can be called from blueprints/script and should keep only single movement request at the same time. // this function is entry point of all movement mechanics - do NOT abort in here, since movement may be handled by AITasks, which support stacking SCOPE_CYCLE_COUNTER(STAT_MoveTo); UE_VLOG(this, LogAINavigation, Log, TEXT("MoveTo: %s"), *MoveRequest.ToString()); FPathFollowingRequestResult ResultData; ResultData.Code = EPathFollowingRequestResult::Failed; if (MoveRequest.IsValid() == false) { UE_VLOG(this, LogAINavigation, Error, TEXT("MoveTo request failed due MoveRequest not being valid. Most probably desireg Goal Actor not longer exists"), *MoveRequest.ToString()); return ResultData; } if (PathFollowingComponent == nullptr) { UE_VLOG(this, LogAINavigation, Error, TEXT("MoveTo request failed due missing PathFollowingComponent")); return ResultData; } ensure(MoveRequest.GetNavigationFilter() || !DefaultNavigationFilterClass); bool bCanRequestMove = true; bool bAlreadyAtGoal = false; if (!MoveRequest.IsMoveToActorRequest()) { if (MoveRequest.GetGoalLocation().ContainsNaN() || FAISystem::IsValidLocation(MoveRequest.GetGoalLocation()) == false) { UE_VLOG(this, LogAINavigation, Error, TEXT("AAIController::MoveTo: Destination is not valid! Goal(%s)"), TEXT_AI_LOCATION(MoveRequest.GetGoalLocation())); bCanRequestMove = false; } // fail if projection to navigation is required but it failed if (bCanRequestMove && MoveRequest.IsProjectingGoal()) { UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GetWorld()); const FNavAgentProperties& AgentProps = GetNavAgentPropertiesRef(); FNavLocation ProjectedLocation; if (NavSys && !NavSys->ProjectPointToNavigation(MoveRequest.GetGoalLocation(), ProjectedLocation, INVALID_NAVEXTENT, &AgentProps)) { UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh")); bCanRequestMove = false; } MoveRequest.UpdateGoalLocation(ProjectedLocation.Location); } bAlreadyAtGoal = bCanRequestMove && PathFollowingComponent->HasReached(MoveRequest); } else { bAlreadyAtGoal = bCanRequestMove && PathFollowingComponent->HasReached(MoveRequest); } if (bAlreadyAtGoal) { UE_VLOG(this, LogAINavigation, Log, TEXT("MoveTo: already at goal!")); ResultData.MoveId = PathFollowingComponent->RequestMoveWithImmediateFinish(EPathFollowingResult::Success); ResultData.Code = EPathFollowingRequestResult::AlreadyAtGoal; } else if (bCanRequestMove) { FPathFindingQuery PFQuery; const bool bValidQuery = BuildPathfindingQuery(MoveRequest, PFQuery); if (bValidQuery) { FNavPathSharedPtr Path; FindPathForMoveRequest(MoveRequest, PFQuery, Path); const FAIRequestID RequestID = Path.IsValid() ? RequestMove(MoveRequest, Path) : FAIRequestID::InvalidRequest; if (RequestID.IsValid()) { bAllowStrafe = MoveRequest.CanStrafe(); ResultData.MoveId = RequestID; ResultData.Code = EPathFollowingRequestResult::RequestSuccessful; if (OutPath) { *OutPath = Path; } } } } if (ResultData.Code == EPathFollowingRequestResult::Failed) { ResultData.MoveId = PathFollowingComponent->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid); } return ResultData; }
void ANavigationData::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) { Super::TickActor(DeltaTime, TickType, ThisTickFunction); PurgeUnusedPaths(); INC_DWORD_STAT_BY(STAT_Navigation_ObservedPathsCount, ObservedPaths.Num()); if (NextObservedPathsTickInSeconds >= 0.f) { NextObservedPathsTickInSeconds -= DeltaTime; if (NextObservedPathsTickInSeconds <= 0.f) { RepathRequests.Reserve(ObservedPaths.Num()); for (int32 PathIndex = ObservedPaths.Num() - 1; PathIndex >= 0; --PathIndex) { if (ObservedPaths[PathIndex].IsValid()) { FNavPathSharedPtr SharedPath = ObservedPaths[PathIndex].Pin(); FNavigationPath* Path = SharedPath.Get(); EPathObservationResult::Type Result = Path->TickPathObservation(); switch (Result) { case EPathObservationResult::NoLongerObserving: ObservedPaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false); break; case EPathObservationResult::NoChange: // do nothing break; case EPathObservationResult::RequestRepath: RepathRequests.Add(FNavPathRecalculationRequest(SharedPath, ENavPathUpdateType::GoalMoved)); break; default: check(false && "unhandled EPathObservationResult::Type in ANavigationData::TickActor"); break; } } else { ObservedPaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false); } } if (ObservedPaths.Num() > 0) { NextObservedPathsTickInSeconds = ObservedPathsTickInterval; } } } if (RepathRequests.Num() > 0) { float TimeStamp = GetWorldTimeStamp(); TArray<FNavPathRecalculationRequest> PostponedRequests; const UWorld* World = GetWorld(); // @todo batch-process it! for (auto RecalcRequest : RepathRequests) { // check if it can be updated right now FNavPathSharedPtr PinnedPath = RecalcRequest.Path.Pin(); if (PinnedPath.IsValid() == false) { continue; } const UObject* PathQuerier = PinnedPath->GetQuerier(); const INavAgentInterface* PathNavAgent = Cast<const INavAgentInterface>(PathQuerier); if (PathNavAgent && PathNavAgent->ShouldPostponePathUpdates()) { PostponedRequests.Add(RecalcRequest); continue; } FPathFindingQuery Query(PinnedPath.ToSharedRef()); // @todo consider supplying NavAgentPropertied from path's querier const FPathFindingResult Result = FindPath(FNavAgentProperties(), Query.SetPathInstanceToUpdate(PinnedPath)); // update time stamp to give observers any means of telling if it has changed PinnedPath->SetTimeStamp(TimeStamp); // partial paths are still valid and can change to full path when moving goal gets back on navmesh if (Result.IsSuccessful() || Result.IsPartial()) { PinnedPath->UpdateLastRepathGoalLocation(); PinnedPath->DoneUpdating(RecalcRequest.Reason); if (RecalcRequest.Reason == ENavPathUpdateType::NavigationChanged) { RegisterActivePath(PinnedPath); } } else { PinnedPath->RePathFailed(); } } RepathRequests.Reset(); RepathRequests.Append(PostponedRequests); } }