// Copyright Epic Games, Inc. All Rights Reserved. #include "AIController.h" #include "CollisionQueryParams.h" #include "NavigationSystem.h" #include "Components/CapsuleComponent.h" #include "GameFramework/PhysicsVolume.h" #include "Engine/Canvas.h" #include "BehaviorTree/BehaviorTreeComponent.h" #include "Tasks/AITask.h" #include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h" #include "Perception/AIPerceptionComponent.h" #include "VisualLogger/VisualLoggerTypes.h" #include "VisualLogger/VisualLogger.h" #include "GameplayTaskResource.h" #include "AIResources.h" #include "AIModuleLog.h" #include "Kismet/GameplayStatics.h" #include "DisplayDebugHelpers.h" #include "BehaviorTree/BehaviorTree.h" #include "GameplayTasksComponent.h" #include "Tasks/GameplayTask_ClaimResource.h" #include "NetworkingDistanceConstants.h" #include "Navigation/PathFollowingComponent.h" #include "NavFilters/NavigationQueryFilter.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(AIController) //----------------------------------------------------------------------// // AAIController //----------------------------------------------------------------------// bool AAIController::bAIIgnorePlayers = false; DECLARE_CYCLE_STAT(TEXT("MoveTo"), STAT_MoveTo, STATGROUP_AI); DEFINE_LOG_CATEGORY(LogAINavigation); AAIController::AAIController(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bSetControlRotationFromPawnOrientation = true; PathFollowingComponent = CreateOptionalDefaultSubobject(TEXT("PathFollowingComponent")); if (PathFollowingComponent) { PathFollowingComponent->OnRequestFinished.AddUObject(this, &AAIController::OnMoveCompleted); } bSkipExtraLOSChecks = true; bWantsPlayerState = false; TeamID = FGenericTeamId::NoTeam; bStartAILogicOnPossess = false; bStopAILogicOnUnposses = true; } void AAIController::Tick(float DeltaTime) { Super::Tick(DeltaTime); UpdateControlRotation(DeltaTime); } void AAIController::PostInitializeComponents() { Super::PostInitializeComponents(); if (bWantsPlayerState && IsValidChecked(this) && (GetNetMode() != NM_Client)) { InitPlayerState(); } if (BrainComponent == nullptr) { BrainComponent = FindComponentByClass(); } if (Blackboard == nullptr) { Blackboard = FindComponentByClass(); } #if ENABLE_VISUAL_LOG for (UActorComponent* Component : GetComponents()) { if (Component) { REDIRECT_OBJECT_TO_VLOG(Component, this); } } #endif // ENABLE_VISUAL_LOG } void AAIController::PostRegisterAllComponents() { Super::PostRegisterAllComponents(); // cache PerceptionComponent if not already set // note that it's possible for an AI to not have a perception component at all if (!IsValid(PerceptionComponent)) { PerceptionComponent = FindComponentByClass(); } } void AAIController::Reset() { Super::Reset(); if (PathFollowingComponent) { PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::OwnerFinished | FPathFollowingResultFlags::ForcedScript); } } void AAIController::DisplayDebug(UCanvas* Canvas, const FDebugDisplayInfo& DebugDisplay, float& YL, float& YPos) { Super::DisplayDebug(Canvas, DebugDisplay, YL, YPos); if (DebugDisplay.IsDisplayOn(NAME_AI)) { if (PathFollowingComponent) { PathFollowingComponent->DisplayDebug(Canvas, DebugDisplay, YL, YPos); } AActor* FocusActor = GetFocusActor(); if (FocusActor) { FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager; DisplayDebugManager.DrawString(FString::Printf(TEXT(" Focus %s"), *FocusActor->GetName())); } } } #if ENABLE_VISUAL_LOG void AAIController::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const { FVisualLogStatusCategory MyCategory; MyCategory.Category = TEXT("AI Controller"); MyCategory.Add(TEXT("Pawn"), GetNameSafe(GetPawn())); AActor* FocusActor = GetFocusActor(); MyCategory.Add(TEXT("Focus"), GetDebugName(FocusActor)); if (FocusActor == nullptr) { MyCategory.Add(TEXT("Focus Location"), TEXT_AI_LOCATION(GetFocalPoint())); } Snapshot->Status.Add(MyCategory); if (GetPawn()) { Snapshot->Location = GetPawn()->GetActorLocation(); } if (PathFollowingComponent) { PathFollowingComponent->DescribeSelfToVisLog(Snapshot); } if (BrainComponent != nullptr) { BrainComponent->DescribeSelfToVisLog(Snapshot); } if (PerceptionComponent != nullptr) { PerceptionComponent->DescribeSelfToVisLog(Snapshot); } } #endif // ENABLE_VISUAL_LOG void AAIController::SetFocalPoint(FVector NewFocus, EAIFocusPriority::Type InPriority) { // clear out existing ClearFocus(InPriority); // now set new focus if (InPriority >= FocusInformation.Priorities.Num()) { FocusInformation.Priorities.SetNum(InPriority + 1); } FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[InPriority]; FocusItem.Position = NewFocus; } FVector AAIController::GetFocalPointForPriority(EAIFocusPriority::Type InPriority) const { FVector Result = FAISystem::InvalidLocation; if (InPriority < FocusInformation.Priorities.Num()) { const FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[InPriority]; AActor* FocusActor = FocusItem.Actor.Get(); if (FocusActor) { Result = GetFocalPointOnActor(FocusActor); } else { Result = FocusItem.Position; } } return Result; } FVector AAIController::GetFocalPoint() const { FVector Result = FAISystem::InvalidLocation; // find focus with highest priority for (int32 Index = FocusInformation.Priorities.Num() - 1; Index >= 0; --Index) { const FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[Index]; AActor* FocusActor = FocusItem.Actor.Get(); if (FocusActor) { Result = GetFocalPointOnActor(FocusActor); break; } else if (FAISystem::IsValidLocation(FocusItem.Position)) { Result = FocusItem.Position; break; } } return Result; } AActor* AAIController::GetFocusActor() const { AActor* FocusActor = nullptr; for (int32 Index = FocusInformation.Priorities.Num() - 1; Index >= 0; --Index) { const FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[Index]; FocusActor = FocusItem.Actor.Get(); if (FocusActor) { break; } else if (FAISystem::IsValidLocation(FocusItem.Position)) { break; } } return FocusActor; } FVector AAIController::GetFocalPointOnActor(const AActor *Actor) const { return Actor != nullptr ? Actor->GetActorLocation() : FAISystem::InvalidLocation; } void AAIController::K2_SetFocus(AActor* NewFocus) { SetFocus(NewFocus, EAIFocusPriority::Gameplay); } void AAIController::K2_SetFocalPoint(FVector NewFocus) { SetFocalPoint(NewFocus, EAIFocusPriority::Gameplay); } void AAIController::K2_ClearFocus() { ClearFocus(EAIFocusPriority::Gameplay); } void AAIController::SetFocus(AActor* NewFocus, EAIFocusPriority::Type InPriority) { // clear out existing ClearFocus(InPriority); // now set new if (NewFocus) { if (InPriority >= FocusInformation.Priorities.Num()) { FocusInformation.Priorities.SetNum(InPriority + 1); } FocusInformation.Priorities[InPriority].Actor = NewFocus; } } void AAIController::ClearFocus(EAIFocusPriority::Type InPriority) { if (InPriority < FocusInformation.Priorities.Num()) { FocusInformation.Priorities[InPriority].Actor = nullptr; FocusInformation.Priorities[InPriority].Position = FAISystem::InvalidLocation; } } void AAIController::SetPerceptionComponent(UAIPerceptionComponent& InPerceptionComponent) { if (PerceptionComponent != nullptr) { UE_VLOG(this, LogAIPerception, Warning, TEXT("Setting perception component while AIController already has one!")); } PerceptionComponent = &InPerceptionComponent; } bool AAIController::LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const { if (Other == nullptr) { return false; } if (ViewPoint.IsZero()) { FRotator ViewRotation; GetActorEyesViewPoint(ViewPoint, ViewRotation); // if we still don't have a view point we simply fail if (ViewPoint.IsZero()) { return false; } } FVector TargetLocation = Other->GetTargetLocation(GetPawn()); FCollisionQueryParams CollisionParams(SCENE_QUERY_STAT(LineOfSight), true, this->GetPawn()); CollisionParams.AddIgnoredActor(Other); bool bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, TargetLocation, ECC_Visibility, CollisionParams); if (!bHit) { return true; } // if other isn't using a cylinder for collision and isn't a Pawn (which already requires an accurate cylinder for AI) // then don't go any further as it likely will not be tracing to the correct location const APawn * OtherPawn = Cast(Other); if (!OtherPawn && Cast(Other->GetRootComponent()) == NULL) { return false; } const FVector OtherActorLocation = Other->GetActorLocation(); const FVector::FReal DistSq = (OtherActorLocation - ViewPoint).SizeSquared(); if (DistSq > FARSIGHTTHRESHOLDSQUARED) { return false; } if (!OtherPawn && (DistSq > NEARSIGHTTHRESHOLDSQUARED)) { return false; } float OtherRadius, OtherHeight; Other->GetSimpleCollisionCylinder(OtherRadius, OtherHeight); if (!bAlternateChecks || !bLOSflag) { //try viewpoint to head bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, OtherActorLocation + FVector(0.f, 0.f, OtherHeight), ECC_Visibility, CollisionParams); if (!bHit) { return true; } } if (!bSkipExtraLOSChecks && (!bAlternateChecks || bLOSflag)) { // only check sides if width of other is significant compared to distance if (OtherRadius * OtherRadius / (OtherActorLocation - ViewPoint).SizeSquared() < 0.0001f) { return false; } //try checking sides - look at dist to four side points, and cull furthest and closest FVector Points[4]; Points[0] = OtherActorLocation - FVector(OtherRadius, -1 * OtherRadius, 0); Points[1] = OtherActorLocation + FVector(OtherRadius, OtherRadius, 0); Points[2] = OtherActorLocation - FVector(OtherRadius, OtherRadius, 0); Points[3] = OtherActorLocation + FVector(OtherRadius, -1 * OtherRadius, 0); int32 IndexMin = 0; int32 IndexMax = 0; FVector::FReal CurrentMax = (Points[0] - ViewPoint).SizeSquared(); FVector::FReal CurrentMin = CurrentMax; for (int32 PointIndex = 1; PointIndex<4; PointIndex++) { const FVector::FReal NextSize = (Points[PointIndex] - ViewPoint).SizeSquared(); if (NextSize > CurrentMin) { CurrentMin = NextSize; IndexMax = PointIndex; } else if (NextSize < CurrentMax) { CurrentMax = NextSize; IndexMin = PointIndex; } } for (int32 PointIndex = 0; PointIndex<4; PointIndex++) { if ((PointIndex != IndexMin) && (PointIndex != IndexMax)) { bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, Points[PointIndex], ECC_Visibility, CollisionParams); if (!bHit) { return true; } } } } return false; } void AAIController::ActorsPerceptionUpdated(const TArray& UpdatedActors) { } DEFINE_LOG_CATEGORY_STATIC(LogTestAI, All, All); void AAIController::UpdateControlRotation(float DeltaTime, bool bUpdatePawn) { APawn* const MyPawn = GetPawn(); if (MyPawn) { FRotator NewControlRotation = GetControlRotation(); // Look toward focus const FVector FocalPoint = GetFocalPoint(); if (FAISystem::IsValidLocation(FocalPoint)) { NewControlRotation = (FocalPoint - MyPawn->GetPawnViewLocation()).Rotation(); } else if (bSetControlRotationFromPawnOrientation) { NewControlRotation = MyPawn->GetActorRotation(); } // Don't pitch view unless looking at another pawn if (NewControlRotation.Pitch != 0 && Cast(GetFocusActor()) == nullptr) { NewControlRotation.Pitch = 0.f; } SetControlRotation(NewControlRotation); if (bUpdatePawn) { const FRotator CurrentPawnRotation = MyPawn->GetActorRotation(); if (CurrentPawnRotation.Equals(NewControlRotation, 1e-3f) == false) { MyPawn->FaceRotation(NewControlRotation, DeltaTime); } } } } void AAIController::OnPossess(APawn* InPawn) { // don't even try possessing pending-kill pawns if (InPawn != nullptr && !IsValid(InPawn)) { return; } Super::OnPossess(InPawn); if (GetPawn() == nullptr || InPawn == nullptr) { return; } // not calling UpdateNavigationComponents() anymore. The PathFollowingComponent // is now observing newly possessed pawns (via OnNewPawn) if (PathFollowingComponent) { PathFollowingComponent->Initialize(); } if (bWantsPlayerState) { ChangeState(NAME_Playing); } // a Pawn controlled by AI _requires_ a GameplayTasksComponent, so if Pawn // doesn't have one we need to create it if (CachedGameplayTasksComponent == nullptr) { UGameplayTasksComponent* GTComp = InPawn->FindComponentByClass(); if (GTComp == nullptr) { GTComp = NewObject(InPawn, TEXT("GameplayTasksComponent")); GTComp->RegisterComponent(); } CachedGameplayTasksComponent = GTComp; } if (CachedGameplayTasksComponent && !CachedGameplayTasksComponent->OnClaimedResourcesChange.Contains(this, GET_FUNCTION_NAME_CHECKED(AAIController, OnGameplayTaskResourcesClaimed))) { CachedGameplayTasksComponent->OnClaimedResourcesChange.AddDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed); } if (Blackboard && Blackboard->GetBlackboardAsset()) { InitializeBlackboard(*Blackboard, *Blackboard->GetBlackboardAsset()); } if (bStartAILogicOnPossess && BrainComponent) { BrainComponent->StartLogic(); } } void AAIController::OnUnPossess() { APawn* CurrentPawn = GetPawn(); Super::OnUnPossess(); if (PathFollowingComponent) { PathFollowingComponent->Cleanup(); } const bool bGameplayTaskCleanupRequired = (CachedGameplayTasksComponent && (CachedGameplayTasksComponent->GetOwner() == CurrentPawn)); // End all tasks owned by the controller before cleaning up the BrainComponent // since it might need to update its resource lock (see OnGameplayTaskResourcesClaimed) if (bGameplayTaskCleanupRequired) { CachedGameplayTasksComponent->EndAllResourceConsumingTasksOwnedBy(*this); } if (bStopAILogicOnUnposses) { CleanupBrainComponent(); } // Can now unregister callback and clear the cache if (bGameplayTaskCleanupRequired) { CachedGameplayTasksComponent->OnClaimedResourcesChange.RemoveDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed); CachedGameplayTasksComponent = nullptr; } } void AAIController::SetPawn(APawn* InPawn) { Super::SetPawn(InPawn); if (Blackboard) { const UBlackboardData* BBAsset = Blackboard->GetBlackboardAsset(); if (BBAsset) { const FBlackboard::FKey SelfKey = BBAsset->GetKeyID(FBlackboard::KeySelf); if (SelfKey != FBlackboard::InvalidKey) { Blackboard->SetValue(SelfKey, GetPawn()); } } } } EPathFollowingRequestResult::Type AAIController::MoveToActor(AActor* Goal, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bCanStrafe, TSubclassOf FilterClass, bool bAllowPartialPaths) { // abort active movement to keep only one request running if (PathFollowingComponent && PathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle) { PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest , FAIRequestID::CurrentRequest, EPathFollowingVelocityMode::Keep); } FAIMoveRequest MoveReq(Goal); MoveReq.SetUsePathfinding(bUsePathfinding); MoveReq.SetAllowPartialPath(bAllowPartialPaths); MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass); MoveReq.SetAcceptanceRadius(AcceptanceRadius); MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap); MoveReq.SetCanStrafe(bCanStrafe); return MoveTo(MoveReq); } EPathFollowingRequestResult::Type AAIController::MoveToLocation(const FVector& Dest, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf FilterClass, bool bAllowPartialPaths) { // abort active movement to keep only one request running if (PathFollowingComponent && PathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle) { PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest , FAIRequestID::CurrentRequest, EPathFollowingVelocityMode::Keep); } FAIMoveRequest MoveReq(Dest); MoveReq.SetUsePathfinding(bUsePathfinding); MoveReq.SetAllowPartialPath(bAllowPartialPaths); MoveReq.SetProjectGoalLocation(bProjectDestinationToNavigation); MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass); MoveReq.SetAcceptanceRadius(AcceptanceRadius); MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap); MoveReq.SetCanStrafe(bCanStrafe); return MoveTo(MoveReq); } void AAIController::MergePaths(const FNavPathSharedPtr& InitialPath, FNavPathSharedPtr& InOutMergedPath) const { if (!InitialPath.IsValid() || !InitialPath->IsValid()) { UE_VLOG_UELOG(this, LogAINavigation, Error, TEXT("%hs: InitialPath is Invalid"), __FUNCTION__); return; } if (!InOutMergedPath.IsValid() || !InOutMergedPath->IsValid()) { UE_VLOG_UELOG(this, LogAINavigation, Error, TEXT("%hs: InOutMergedPath is Invalid"), __FUNCTION__); return; } const TArray& InitialPathPoints = InitialPath->GetPathPoints(); TArray& InOutPathPoints = InOutMergedPath->GetPathPoints(); if (!InitialPathPoints.Last().Location.Equals(InOutPathPoints[0].Location)) { UE_VLOG_UELOG(this, LogAINavigation, Error, TEXT("%hs: last %s and first %s points don't match."), __FUNCTION__, *InitialPathPoints.Last().Location.ToString(), *InOutPathPoints[0].Location.ToString()); return; } // We don't want to keep path points that have already been traversed, so only merge the points starting from "CurrentPathIndex". const int32 StartingPointIndex = PathFollowingComponent ? PathFollowingComponent->GetCurrentPathIndex() : 0; if (StartingPointIndex < InitialPathPoints.Num()) { InOutPathPoints.Insert(&InitialPathPoints[StartingPointIndex], InitialPathPoints.Num() - StartingPointIndex - 1, 0); } } 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 desired Goal Actor not longer exists. MoveRequest: '%s'"), *MoveRequest.ToString()); return ResultData; } if (PathFollowingComponent == nullptr) { UE_VLOG(this, LogAINavigation, Error, TEXT("MoveTo request failed due missing PathFollowingComponent")); return ResultData; } 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()) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); const FNavAgentProperties& AgentProps = GetNavAgentPropertiesRef(); FNavLocation ProjectedLocation; if (NavSys && !NavSys->ProjectPointToNavigation(MoveRequest.GetGoalLocation(), ProjectedLocation, INVALID_NAVEXTENT, &AgentProps)) { if (MoveRequest.IsUsingPathfinding()) { UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh")); } else { UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh, path finding is disabled perhaps disable goal projection ?")); } 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; bool bShouldMergePaths = false; FVector StartLocation = GetNavAgentLocation(); if (MoveRequest.ShouldStartFromPreviousPath()) { FNavPathSharedPtr CurrentPath = PathFollowingComponent->GetPath(); if (CurrentPath.IsValid() && CurrentPath->IsValid() && CurrentPath->GetPathPoints().Num() > 0) { StartLocation = CurrentPath->GetPathPoints().Last(); bShouldMergePaths = true; } } const bool bValidQuery = BuildPathfindingQuery(MoveRequest, StartLocation, PFQuery); if (bValidQuery) { FNavPathSharedPtr Path; FindPathForMoveRequest(MoveRequest, PFQuery, Path); if (bShouldMergePaths && Path.IsValid()) { // Merge the newly generated path with the current one MergePaths(PathFollowingComponent->GetPath(), 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; } FAIRequestID AAIController::RequestMove(const FAIMoveRequest& MoveRequest, FNavPathSharedPtr Path) { uint32 RequestID = FAIRequestID::InvalidRequest; if (PathFollowingComponent) { RequestID = PathFollowingComponent->RequestMove(MoveRequest, Path); } return RequestID; } bool AAIController::PauseMove(FAIRequestID RequestToPause) { if (PathFollowingComponent != NULL && RequestToPause.IsEquivalent(PathFollowingComponent->GetCurrentRequestId())) { PathFollowingComponent->PauseMove(RequestToPause, EPathFollowingVelocityMode::Reset); return true; } return false; } bool AAIController::ResumeMove(FAIRequestID RequestToResume) { if (PathFollowingComponent != NULL && RequestToResume.IsEquivalent(PathFollowingComponent->GetCurrentRequestId())) { PathFollowingComponent->ResumeMove(RequestToResume); return true; } return false; } void AAIController::StopMovement() { // @note FPathFollowingResultFlags::ForcedScript added to make AITask_MoveTo instances // not ignore OnRequestFinished notify that's going to be sent out due to this call if (PathFollowingComponent) { PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::MovementStop | FPathFollowingResultFlags::ForcedScript); } } bool AAIController::ShouldPostponePathUpdates() const { if (PathFollowingComponent && PathFollowingComponent->HasStartedNavLinkMove()) { return true; } return Super::ShouldPostponePathUpdates(); } bool AAIController::BuildPathfindingQuery(const FAIMoveRequest& MoveRequest, FPathFindingQuery& OutQuery) const { return BuildPathfindingQuery(MoveRequest, GetNavAgentLocation(), OutQuery); } bool AAIController::BuildPathfindingQuery(const FAIMoveRequest& MoveRequest, const FVector& StartLocation, FPathFindingQuery& OutQuery) const { bool bResult = false; UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); const ANavigationData* NavData = (NavSys == nullptr) ? nullptr : MoveRequest.IsUsingPathfinding() ? NavSys->GetNavDataForProps(GetNavAgentPropertiesRef(), GetNavAgentLocation()) : NavSys->GetAbstractNavData(); if (NavData) { FVector GoalLocation = MoveRequest.GetGoalLocation(); if (MoveRequest.IsMoveToActorRequest()) { const INavAgentInterface* NavGoal = Cast(MoveRequest.GetGoalActor()); if (NavGoal) { const FVector Offset = NavGoal->GetMoveGoalOffset(this); GoalLocation = FQuatRotationTranslationMatrix(MoveRequest.GetGoalActor()->GetActorQuat(), NavGoal->GetNavAgentLocation()).TransformPosition(Offset); } else { GoalLocation = MoveRequest.GetGoalActor()->GetActorLocation(); } } FSharedConstNavQueryFilter NavFilter = UNavigationQueryFilter::GetQueryFilter(*NavData, this, MoveRequest.GetNavigationFilter()); OutQuery = FPathFindingQuery(*this, *NavData, StartLocation, GoalLocation, NavFilter); OutQuery.SetAllowPartialPaths(MoveRequest.IsUsingPartialPaths()); OutQuery.SetRequireNavigableEndLocation(MoveRequest.IsNavigableEndLocationRequired()); if (MoveRequest.IsApplyingCostLimitFromHeuristic()) { const float HeuristicScale = NavFilter->GetHeuristicScale(); OutQuery.CostLimit = FPathFindingQuery::ComputeCostLimitFromHeuristic(OutQuery.StartLocation, OutQuery.EndLocation, HeuristicScale, MoveRequest.GetCostLimitFactor(), MoveRequest.GetMinimumCostLimit()); } if (PathFollowingComponent) { PathFollowingComponent->OnPathfindingQuery(OutQuery); } bResult = true; } else { if (NavSys == nullptr) { UE_VLOG(this, LogAINavigation, Warning, TEXT("Unable AAIController::BuildPathfindingQuery due to no NavigationSystem present. Note that even pathfinding-less movement requires presence of NavigationSystem.")); } else { UE_VLOG(this, LogAINavigation, Warning, TEXT("Unable to find NavigationData instance while calling AAIController::BuildPathfindingQuery")); } } return bResult; } void AAIController::FindPathForMoveRequest(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query, FNavPathSharedPtr& OutPath) const { SCOPE_CYCLE_COUNTER(STAT_AI_Overall); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (NavSys) { FPathFindingResult PathResult = NavSys->FindPathSync(Query); if (PathResult.Result != ENavigationQueryResult::Error) { if (PathResult.IsSuccessful() && PathResult.Path.IsValid()) { if (MoveRequest.IsMoveToActorRequest()) { PathResult.Path->SetGoalActorObservation(*MoveRequest.GetGoalActor(), 100.0f); } PathResult.Path->EnableRecalculationOnInvalidation(true); OutPath = PathResult.Path; } } else { UE_VLOG(this, LogAINavigation, Error, TEXT("Trying to find path to %s resulted in Error") , MoveRequest.IsMoveToActorRequest() ? *GetNameSafe(MoveRequest.GetGoalActor()) : *MoveRequest.GetGoalLocation().ToString()); UE_VLOG_SEGMENT(this, LogAINavigation, Error, GetPawn() ? GetPawn()->GetActorLocation() : FAISystem::InvalidLocation , MoveRequest.GetGoalLocation(), FColor::Red, TEXT("Failed move to %s"), *GetNameSafe(MoveRequest.GetGoalActor())); } } } // DEPRECATED FUNCTION SUPPORT bool AAIController::PreparePathfinding(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query) { return BuildPathfindingQuery(MoveRequest, Query); } // 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; } EPathFollowingStatus::Type AAIController::GetMoveStatus() const { return (PathFollowingComponent) ? PathFollowingComponent->GetStatus() : EPathFollowingStatus::Idle; } bool AAIController::HasPartialPath() const { return (PathFollowingComponent != NULL) && (PathFollowingComponent->HasPartialPath()); } bool AAIController::IsFollowingAPath() const { return (PathFollowingComponent != nullptr) && (PathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle); } IPathFollowingAgentInterface* AAIController::GetPathFollowingAgent() const { return PathFollowingComponent; } FVector AAIController::GetImmediateMoveDestination() const { return (PathFollowingComponent) ? PathFollowingComponent->GetCurrentTargetLocation() : FVector::ZeroVector; } void AAIController::SetMoveBlockDetection(bool bEnable) { if (PathFollowingComponent) { PathFollowingComponent->SetBlockDetectionState(bEnable); } } void AAIController::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) { ReceiveMoveCompleted.Broadcast(RequestID, Result.Code); OnMoveCompleted(RequestID, Result.Code); } void AAIController::OnMoveCompleted(FAIRequestID RequestID, EPathFollowingResult::Type Result) { // deprecated } FAIRequestID AAIController::GetCurrentMoveRequestID() const { return GetPathFollowingComponent() ? GetPathFollowingComponent()->GetCurrentRequestId() : FAIRequestID::InvalidRequest; } bool AAIController::RunBehaviorTree(UBehaviorTree* BTAsset) { // @todo: find BrainComponent and see if it's BehaviorTreeComponent // Also check if BTAsset requires BlackBoardComponent, and if so // check if BB type is accepted by BTAsset. // Spawn BehaviorTreeComponent if none present. // Spawn BlackBoardComponent if none present, but fail if one is present but is not of compatible class if (BTAsset == NULL) { UE_VLOG(this, LogBehaviorTree, Warning, TEXT("RunBehaviorTree: Unable to run NULL behavior tree")); return false; } bool bSuccess = true; // see if need a blackboard component at all UBlackboardComponent* BlackboardComp = Blackboard; if (BTAsset->BlackboardAsset && (Blackboard == nullptr || Blackboard->IsCompatibleWith(BTAsset->BlackboardAsset) == false)) { bSuccess = UseBlackboard(BTAsset->BlackboardAsset, BlackboardComp); } if (bSuccess) { UBehaviorTreeComponent* BTComp = Cast(BrainComponent); if (BTComp == NULL) { UE_VLOG(this, LogBehaviorTree, Log, TEXT("RunBehaviorTree: spawning BehaviorTreeComponent..")); BTComp = NewObject(this, TEXT("BTComponent")); BTComp->RegisterComponent(); REDIRECT_OBJECT_TO_VLOG(BTComp, this); } // make sure BrainComponent points at the newly created BT component BrainComponent = BTComp; check(BTComp != NULL); BTComp->StartTree(*BTAsset, EBTExecutionMode::Looped); } return bSuccess; } void AAIController::CleanupBrainComponent() { if (BrainComponent) { BrainComponent->Cleanup(); } } void AAIController::ClaimTaskResource(TSubclassOf ResourceClass) { if (CachedGameplayTasksComponent) { const uint8 ResourceID = UGameplayTaskResource::GetResourceID(ResourceClass); if (ScriptClaimedResources.HasID(ResourceID) == false) { ScriptClaimedResources.AddID(ResourceID); UE_VLOG(this, LogGameplayTasks, Log, TEXT("ClaimTaskResource %s"), *GetNameSafe(*ResourceClass)); IGameplayTaskOwnerInterface& AsTaskOwner = *this; UGameplayTask_ClaimResource* ResourceTask = UGameplayTask_ClaimResource::ClaimResource(AsTaskOwner, ResourceClass, uint8(EAITaskPriority::High), ResourceClass->GetFName()); if (ResourceTask) { CachedGameplayTasksComponent->AddTaskReadyForActivation(*ResourceTask); } UE_CVLOG(ResourceTask == nullptr, this, LogGameplayTasks, Warning, TEXT("ClaimTaskResource failed to create UGameplayTask_ClaimResource instance")); } } } void AAIController::UnclaimTaskResource(TSubclassOf ResourceClass) { if (CachedGameplayTasksComponent) { const uint8 ResourceID = UGameplayTaskResource::GetResourceID(ResourceClass); if (ScriptClaimedResources.HasID(ResourceID) == true) { ScriptClaimedResources.RemoveID(ResourceID); UE_VLOG(this, LogGameplayTasks, Log, TEXT("UnclaimTaskResource %s"), *GetNameSafe(*ResourceClass)); UGameplayTask* ResourceTask = CachedGameplayTasksComponent->FindResourceConsumingTaskByName(ResourceClass->GetFName()); if (ResourceTask) { ResourceTask->EndTask(); } UE_CVLOG(ResourceTask == nullptr, this, LogGameplayTasks, Warning, TEXT("UnclaimTaskResource failed to find UGameplayTask_ClaimResource instance")); } } } bool AAIController::InitializeBlackboard(UBlackboardComponent& BlackboardComp, UBlackboardData& BlackboardAsset) { check(BlackboardComp.GetOwner() == this); if (BlackboardComp.InitializeBlackboard(BlackboardAsset)) { // find the "self" key and set it to our pawn const FBlackboard::FKey SelfKey = BlackboardAsset.GetKeyID(FBlackboard::KeySelf); if (SelfKey != FBlackboard::InvalidKey) { BlackboardComp.SetValue(SelfKey, GetPawn()); } OnUsingBlackBoard(&BlackboardComp, &BlackboardAsset); return true; } return false; } bool AAIController::UseBlackboard(UBlackboardData* BlackboardAsset, UBlackboardComponent*& BlackboardComponent) { if (BlackboardAsset == nullptr) { UE_VLOG(this, LogBehaviorTree, Log, TEXT("UseBlackboard: trying to use NULL Blackboard asset. Ignoring")); return false; } bool bSuccess = true; Blackboard = FindComponentByClass(); if (Blackboard == nullptr) { Blackboard = NewObject(this, TEXT("BlackboardComponent")); REDIRECT_OBJECT_TO_VLOG(Blackboard, this); if (Blackboard != nullptr) { bSuccess = InitializeBlackboard(*Blackboard, *BlackboardAsset); Blackboard->RegisterComponent(); } } else if (Blackboard->GetBlackboardAsset() == nullptr) { bSuccess = InitializeBlackboard(*Blackboard, *BlackboardAsset); } else if (Blackboard->GetBlackboardAsset() != BlackboardAsset) { // @todo this behavior should be opt-out-able. UE_VLOG(this, LogBehaviorTree, Log, TEXT("UseBlackboard: requested blackboard %s while already has %s instantiated. Forcing new BB.") , *GetNameSafe(BlackboardAsset), *GetNameSafe(Blackboard->GetBlackboardAsset())); bSuccess = InitializeBlackboard(*Blackboard, *BlackboardAsset); } BlackboardComponent = Blackboard; return bSuccess; } bool AAIController::ShouldSyncBlackboardWith(const UBlackboardComponent& OtherBlackboardComponent) const { return Blackboard != nullptr && Blackboard->GetBlackboardAsset() != nullptr && OtherBlackboardComponent.GetBlackboardAsset() != nullptr && Blackboard->GetBlackboardAsset()->IsRelatedTo(*OtherBlackboardComponent.GetBlackboardAsset()); } bool AAIController::SuggestTossVelocity(FVector& OutTossVelocity, FVector Start, FVector End, float TossSpeed, bool bPreferHighArc, float CollisionRadius, bool bOnlyTraceUp) { // pawn's physics volume gets 2nd priority APhysicsVolume const* const PhysicsVolume = GetPawn() ? GetPawn()->GetPhysicsVolume() : nullptr; float const GravityOverride = PhysicsVolume ? PhysicsVolume->GetGravityZ() : 0.f; ESuggestProjVelocityTraceOption::Type const TraceOption = bOnlyTraceUp ? ESuggestProjVelocityTraceOption::OnlyTraceWhileAscending : ESuggestProjVelocityTraceOption::TraceFullPath; UGameplayStatics::FSuggestProjectileVelocityParameters VelocityParams = UGameplayStatics::FSuggestProjectileVelocityParameters(this, Start, End, TossSpeed); VelocityParams.bFavorHighArc = bPreferHighArc; VelocityParams.CollisionRadius = CollisionRadius; VelocityParams.OverrideGravityZ = GravityOverride; VelocityParams.TraceOption = TraceOption; return UGameplayStatics::SuggestProjectileVelocity(VelocityParams, OutTossVelocity); } FString AAIController::GetDebugIcon() const { if (BrainComponent == NULL || BrainComponent->IsRunning() == false) { return TEXT("/Engine/EngineResources/AICON-Red.AICON-Red"); } return TEXT("/Engine/EngineResources/AICON-Green.AICON-Green"); } void AAIController::OnGameplayTaskResourcesClaimed(FGameplayResourceSet NewlyClaimed, FGameplayResourceSet FreshlyReleased) { if (BrainComponent) { const uint8 LogicID = UGameplayTaskResource::GetResourceID(); if (NewlyClaimed.HasID(LogicID)) { BrainComponent->LockResource(EAIRequestPriority::Logic); } else if (FreshlyReleased.HasID(LogicID)) { BrainComponent->ClearResourceLock(EAIRequestPriority::Logic); } } } void AAIController::SetPathFollowingComponent(UPathFollowingComponent* NewPFComponent) { PathFollowingComponent = NewPFComponent; #if ENABLE_VISUAL_LOG if (NewPFComponent) { REDIRECT_OBJECT_TO_VLOG(NewPFComponent, this); } #endif // ENABLE_VISUAL_LOG } //----------------------------------------------------------------------// // IGenericTeamAgentInterface //----------------------------------------------------------------------// void AAIController::SetGenericTeamId(const FGenericTeamId& NewTeamID) { if (TeamID != NewTeamID) { TeamID = NewTeamID; // @todo notify perception system that a controller changed team ID } }