UK2Node_AIMoveTo
这是我们最熟悉的编辑器模式下的AI_MoveTo节点,也就是那个自带OnSuccess和OnFailed的异步节点,在蓝图中大多数时候使用起来都得心应手非常方便,但是它有个最大的缺点,异步节点意味着他有自己的生命周期,不能放到函数中使用,同样的,在代码中也没有此类节点,那么接下来我们尝试解决这一问题
翻看此节点的代码,没几行代码,主要看如下
1 2 3 4 5 6 7 8 UK2Node_AIMoveTo::UK2Node_AIMoveTo (const FObjectInitializer& ObjectInitializer) : Super (ObjectInitializer) { ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED (UAIBlueprintHelperLibrary, CreateMoveToProxyObject); ProxyFactoryClass = UAIBlueprintHelperLibrary::StaticClass (); ProxyClass = UAIAsyncTaskBlueprintProxy::StaticClass (); }
我们发现一个关键函数CreateMoveToProxyObject
,找到该函数的原型
UAIBlueprintHelperLibrary::CreateMoveToProxyObject
1 2 3 4 5 6 7 8 9 static UAIAsyncTaskBlueprintProxy * CreateMoveToProxyObject ( UObject * WorldContextObject, APawn * Pawn, FVector Destination, AActor * TargetActor, float AcceptanceRadius, bool bStopOnOverlap )
熟悉的参数,就是我们调用AIMoveTo的参数,继续翻代码
UAIBlueprintHelperLibrary::CreateMoveToProxyObject
UAIBlueprintHelperLibrary
函数库的一个方法,返回UAIAsyncTaskBlueprintProxy
对象
看关键代码
1 UAIAsyncTaskBlueprintProxy* MyObj = NULL ;
这里需要注意一点,UAIAsyncTaskBlueprintProxy
构造以后会存到AISystem
内,防止GC销毁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FAIMoveRequest MoveReq; MoveReq.SetUsePathfinding (true ); MoveReq.SetAcceptanceRadius (AcceptanceRadius); MoveReq.SetReachTestIncludesAgentRadius (bStopOnOverlap); if (TargetActor) { MoveReq.SetGoalActor (TargetActor); } else { MoveReq.SetGoalLocation (Destination); } MoveReq.SetNavigationFilter (AIController->GetDefaultNavigationFilterClass ()); FPathFollowingRequestResult ResultData = AIController->MoveTo (MoveReq);
简单说就是,创建并设置了关键的结构体FAIMoveRequest
,然后塞给AIController->MoveTo(MoveReq);
至于AIController
干了什么,我们后面再说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FPathFollowingRequestResult ResultData = AIController->MoveTo (MoveReq); switch (ResultData.Code){ case EPathFollowingRequestResult::RequestSuccessful: MyObj->AIController = AIController; MyObj->AIController->ReceiveMoveCompleted.AddDynamic (MyObj, &UAIAsyncTaskBlueprintProxy::OnMoveCompleted); MyObj->MoveRequestId = ResultData.MoveId; break ; case EPathFollowingRequestResult::AlreadyAtGoal: World->GetTimerManager ().SetTimer (MyObj->TimerHandle_OnInstantFinish, MyObj, &UAIAsyncTaskBlueprintProxy::OnAtGoal, 0.1f , false ); break ; case EPathFollowingRequestResult::Failed:default : World->GetTimerManager ().SetTimer (MyObj->TimerHandle_OnInstantFinish, MyObj, &UAIAsyncTaskBlueprintProxy::OnNoPath, 0.1f , false ); break ; }
Controller
的MoveTo
方法返回一个结果结构体FPathFollowingRequestResult
,这里用到结构体内的Code
参数,看申明类型是TEnumAsByte<EPathFollowingRequestResult::Type> Code
看一下这个枚举
1 2 3 4 5 6 7 8 9 10 UENUM (BlueprintType)namespace EPathFollowingRequestResult{ enum Type { Failed, AlreadyAtGoal, RequestSuccessful }; }
也就3个成员,分别是失败/已经到达/请求成功
对于3个结果分别绑定到本地3个函数
请求成功:监听AIController内的ReceiveMoveCompleted
,绑定到本地OnMoveCompleted
,移动结果通过本地多播代理的OnFail
或者OnSuccess
广播
失败:0.1秒后执行本地OnNoPath
事件,广播OnFail
,然后从AISystem
移除
已经到达:0.1秒后执行本地事件OnAtGoal
,广播OnSuccess
,然后从AISystem
移除
总结
创建一个代理类,通过AIController
执行移动事件,绑定了AIController
的移动结果回调事件,把结果通过此代理的2个代理广播
至于为什么用2个类型一样的代理,跟K2Node的使用有关系,理论上用1个代理可以实现一样的作用
封装蓝图方法 代理类
我们先来创建一个UObject
类,作为一个移动代理,毕竟移动有生命周期,必须要有个类来管理和监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "Navigation/PathFollowingComponent.h" #include "AIMoveProxy.generated.h" DECLARE_DYNAMIC_DELEGATE_OneParam (FMoveEnd, EPathFollowingResult::Type, MovementResult);class UAIAsyncTaskBlueprintProxy ;class APawn ;UCLASS (BlueprintType,Blueprintable)class INPUTSYS_API UAIMoveProxy : public UObject{ GENERATED_BODY () public : UPROPERTY (BlueprintReadWrite) UAIAsyncTaskBlueprintProxy* Proxy; UFUNCTION (BlueprintCallable) bool Proxy_AIMoveTo (APawn* Pawn,FVector Des,AActor* Target,const FMoveEnd& MoveResult,float AcceptanceRadius=5.0 ,bool StopOnOverlap=false ) ; UFUNCTION () void MoveEnd (EPathFollowingResult::Type Result) ; UPROPERTY () FMoveEnd OnMoveEnd; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "AIMoveProxy.h" #include "Blueprint/AIBlueprintHelperLibrary.h" #include "Blueprint/AIAsyncTaskBlueprintProxy.h" bool UAIMoveProxy::Proxy_AIMoveTo (APawn* Pawn, FVector Des, AActor* Target, const FMoveEnd& MoveResult, float AcceptanceRadius, bool StopOnOverlap) { if (!Pawn) { return false ; } OnMoveEnd = MoveResult; Proxy = UAIBlueprintHelperLibrary::CreateMoveToProxyObject (Pawn, Pawn, Des, Target, AcceptanceRadius, StopOnOverlap); Proxy->OnFail.AddDynamic (this ,&UAIMoveProxy::MoveEnd); Proxy->OnSuccess.AddDynamic (this , &UAIMoveProxy::MoveEnd); return true ; } void UAIMoveProxy::MoveEnd (EPathFollowingResult::Type Result) { OnMoveEnd.ExecuteIfBound (Result); }
到蓝图实现如下图
函数库 但是这样还是不是很方便,每次都要手动创建一个类,于是就封装到了函数库里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #pragma once #include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "AIMoveProxy.h" #include "FLib_AI.generated.h" UCLASS (Blueprintable,BlueprintType)class INPUTSYS_API UFLib_AI : public UBlueprintFunctionLibrary{ GENERATED_BODY () public : UFUNCTION (BlueprintCallable) static UAIMoveProxy* V_AI_MoveTo (APawn* Pawn, FVector Des, AActor* Target, const FMoveEnd& MoveResult, float AcceptanceRadius = 5.0 , bool StopOnOverlap = false ) ; };
1 2 3 4 5 6 7 8 9 10 11 #include "FLib_AI.h" UAIMoveProxy* UFLib_AI::V_AI_MoveTo (APawn* Pawn, FVector Des, AActor* Target, const FMoveEnd& MoveResult, float AcceptanceRadius, bool StopOnOverlap) { if (!Pawn) { return nullptr ; }; UAIMoveProxy* p = NewObject<UAIMoveProxy>(Pawn); p->Proxy_AIMoveTo (Pawn, Des, Target, MoveResult, AcceptanceRadius, StopOnOverlap); return p; }
使用如下图
简单剖析一下 AIController 构造 1 PathFollowingComponent->OnRequestFinished.AddUObject (this , &AAIController::OnMoveCompleted);
绑定了UPathFollowingComponent
的完成事件,我们上面代理监听的事件是从这里来的
MoveTo
先贴代码配注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 FPathFollowingRequestResult AAIController::MoveTo (const FAIMoveRequest& MoveRequest, FNavPathSharedPtr* OutPath) { 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 ; } if (bCanRequestMove && MoveRequest.IsProjectingGoal ()) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(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;
上面的MoveTo
完成了移动的请求即RequestMove
,但是具体哪里在执行角色的移动呢,继续翻代码
RequestMove 1 2 3 4 5 6 7 8 9 10 FAIRequestID AAIController::RequestMove (const FAIMoveRequest& MoveRequest, FNavPathSharedPtr Path) { uint32 RequestID = FAIRequestID::InvalidRequest; if (PathFollowingComponent) { RequestID = PathFollowingComponent->RequestMove (MoveRequest, Path); } return RequestID; }
PathFollowingComponent 一顿操作以后调用UPathFollowingComponent::RequestMove
代码不贴全,茫茫多的判断以后来到关键的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (!bIsUsingMetaPath && Path.IsValid () && Path->IsValid ()){ Status = EPathFollowingStatus::Moving; const uint32 CurrentSegment = DetermineStartingPathPoint (InPath.Get ()); SetMoveSegment (CurrentSegment); } else { Status = EPathFollowingStatus::Waiting; GetWorld ()->GetTimerManager ().SetTimer (WaitingForPathTimer, this , &UPathFollowingComponent::OnWaitingPathTimeout, WaitingTimeout); }
艰难通过前面几道判断(什么Idle,Paused等等我们先不管)以后,把状态参数Status
改为Moving
然后呢?然后这个函数就结束了,继续翻,发现内容就在Tick
里面
TickComponent 1 2 3 4 5 6 7 8 9 10 11 if (Status == EPathFollowingStatus::Moving) { UpdatePathSegment (); } if (Status == EPathFollowingStatus::Moving) { FollowPathSegment (DeltaTime); }
很好奇,为什么同样一个if判断用2次?
继续翻FolowPathSegment
FolowPathSegment 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void UPathFollowingComponent::FollowPathSegment (float DeltaTime) { if (!Path.IsValid () || MovementComp == nullptr ) { return ; } const FVector CurrentLocation = MovementComp->GetActorFeetLocation (); const FVector CurrentTarget = GetCurrentTargetLocation (); bIsDecelerating = false ; const bool bAccelerationBased = MovementComp->UseAccelerationForPathFollowing (); if (bAccelerationBased) { CurrentMoveInput = (CurrentTarget - CurrentLocation).GetSafeNormal (); if (MoveSegmentStartIndex >= DecelerationSegmentIndex) { const FVector PathEnd = Path->GetEndLocation (); const float DistToEndSq = FVector::DistSquared (CurrentLocation, PathEnd); const bool bShouldDecelerate = DistToEndSq < FMath::Square (CachedBrakingDistance); if (bShouldDecelerate) { bIsDecelerating = true ; const float SpeedPct = FMath::Clamp (FMath::Sqrt (DistToEndSq) / CachedBrakingDistance, 0.0f , 1.0f ); CurrentMoveInput *= SpeedPct; } } PostProcessMove.ExecuteIfBound (this , CurrentMoveInput); MovementComp->RequestPathMove (CurrentMoveInput); } else { FVector MoveVelocity = (CurrentTarget - CurrentLocation) / DeltaTime; const int32 LastSegmentStartIndex = Path->GetPathPoints ().Num () - 2 ; const bool bNotFollowingLastSegment = (MoveSegmentStartIndex < LastSegmentStartIndex); PostProcessMove.ExecuteIfBound (this , MoveVelocity); MovementComp->RequestDirectMove (MoveVelocity, bNotFollowingLastSegment); } }
主要就看2个函数MovementComp->RequestPathMove(CurrentMoveInput);
和MovementComp->RequestDirectMove(MoveVelocity, bNotFollowingLastSegment);
前者点进去以后啥也没有,原因是基类不做实现,提示至少要从PawnMovementComponent
开始才有实现
看一下PawnMovementComponent
的实现
OnPathFinished 计算寻路的时候各种判断,最后通过OnRequestFinished
广播给监听者
小结 AI_MoveTo
通过AIController
调用UPathFollowingComponent
的移动请求方法
在AIController
的UPathFollowingComponent
得到移动请求后,调用了Pawn
内的移动组件
的移动方法
PawnMovementComponent 1 2 3 4 5 6 7 void UPawnMovementComponent::RequestPathMove (const FVector& MoveInput) { if (PawnOwner) { PawnOwner->Internal_AddMovementInput (MoveInput); } }
绕了一大圈结果Pawn
就是通过AddMovement
执行路径移动,关于DirectMove
在UPawnMovementComponent
中没有实现
继续看看UCharacterMovementComponent
UCharacterMovementComponent 主要执行移动的函数在PerformMovement
里,此函数在TickComponent
中调用
另外无意中发现了跟其他内容有关系的代码
1 if (MovementMode == MOVE_None || UpdatedComponent->Mobility != EComponentMobility::Movable || UpdatedComponent->IsSimulatingPhysics ())
如果MovementMode
设置了None
或者开启物理模拟了就不能移动
1 2 3 4 if (bHasRootMotionSources && !CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion) { if ( CharacterOwner->IsPlayingRootMotion () && CharacterOwner->GetMesh () ) {
RootMotion
动画优先于移动
1 if (bHasAuthority && UNetDriver::IsAdaptiveNetUpdateFrequencyEnabled () && UpdatedComponent)
只对权威角色开启移动效果
主要通过MaybeUpdateBasedMovement
或者MoveUpdatedComponent
方法(在rootmotion情况下)等方法最后调用到方法MoveUpdatedComponentImpl
,然后调用 UpdatedComponent->MoveComponent
另外
设置了当前的Velocity以及位置和旋转,也存储了Last和New2份
这个Velocity其实就是2帧渲染的位置插值
Pawn AddMovement 1 2 3 4 5 void Internal_AddMovementInput (FVector WorldAccel, bool bForce = false ) ;ControlInputVector += WorldAccel;
如果有UCharacterMovementComponent
就调用组件里的方法(组件也是直接调用Pawn的内部方法)否则调用自身的内部方法Internal_AddMovementInput
同理用于Get方法
改变的这个参数用于派生类计算速度值Velocity
用于,例如UFloatingPawnMovement::ApplyControlInputToVelocity
速度计算要通过USceneComponent
刷新位置顺序 用上面的速度 变量来刷新位置信息
先看调用顺序,从移动组件的Tick
开始
PerformMovement(DeltaTime)
MovementComponent::MoveUpdatedComponentImpl(const FVector& Delta,...)
UpdatedComponent(MovementComponent的parent的rootcomp)->MoveComponent()
UpdatedComponent::MoveComponentImpl()
UpdateComponentToWorld()
UpdateComponentToWorldWithParent()
PropagateTransformUpdate()
到这开始刷新根组件即parent的root的位置、渲染、体积、寻路等信息,同时也刷新子组件的信息,之后引擎的处理可以暂时参考由SetActorLocation分析渲染流程
其他移动方法
诸如SimpleMoveToLocation
等AIController
内的方法最后都是调用了PathFollowingComponent::RequestMove
AI 行为的Task 节点,最终调用的也是AIController
的MoveTo
方法
总结
2020.12.31补充 玩家使用SimpleMoveTo的回调事件 AIMoveTo
不能作用于玩家, 关键在于CreateMoveToProxyObject
函数中的
1 AAIController* AIController = Cast<AAIController>(Pawn->GetController ());
Simple
版本可以作用给玩家, 例如Topdown
模板内就用此来移动玩家, 但是Simple
版本的2个函数没有回调事件, 如果想要得到回调事件如何做?
查看SimpleMoveToActor()
1 UPathFollowingComponent* PFollowComp = InitNavigationControl (*Controller);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 UPathFollowingComponent* InitNavigationControl (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; }
上面代码看到如果不是AIController
也可以, 所以我们玩家才可以使用Simple
版本的移动
那么回调事件其实也在该组件内, 即我们需要一个PathFollowingComp
在我们的PlayerController
内
1 2 3 4 5 ASRPlayerController::ASRPlayerController (const FObjectInitializer& ObjectInitializer) :Super (ObjectInitializer) { PathFollowingComponent = CreateDefaultSubobject<UPathFollowingComponent>(TEXT ("PathFollowingComponent" )); }
1 PathFollowingComponent->OnRequestFinished.AddUObject (this , &ASRPlayerController::MoveComplete);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void ASRPlayerController::MoveComplete (FAIRequestID RequestID, const FPathFollowingResult& Result) { if (Result.IsSuccess ()) { ReceiveMoveComplete (true ,CurrentMoveType); } else { ReceiveMoveComplete (false , CurrentMoveType); } CurrentMoveType = ESRMoveToType::None; } void ASRPlayerController::ReceiveMoveComplete_Implementation (bool bIsSuccess, ESRMoveToType MoveType) { CurrentMoveType = ESRMoveToType::None; }
然后蓝图就可以通过事件ReceiveMoveComplete_Implementation
得到移动结束的消息, 当然有很多扩展方式, 你完全可以自己继承一个PathFollowingComp
来做动态代理广播的方式
停止距离 问题来了, SimpleMoveToActor
没有类似AIMoveTo
的停止距离, 也不方便
扩展
1 2 UFUNCTION (BlueprintCallable, Category = "SR|Lib|Movement" ) static void SimpleMoveToForAllController (AController* Controller, FVector Destination, AActor* Target, float AcceptanceRadius = 5.0f , bool StopOnOverlap= false ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 void UFlib_SRUtilities::SimpleMoveToForAllController (AController* Controller, FVector Destination, AActor* Target, float AcceptanceRadius , bool StopOnOverlap) { UNavigationSystemV1* NavSys = Controller ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(Controller->GetWorld ()) : nullptr ; if (NavSys == nullptr || Controller == nullptr || Controller->GetPawn () == nullptr ) { SRWARNING (TEXT ("SimpleMoveToForAllController [no NavSys or Controller or pawn]" )); return ; } UPathFollowingComponent* PFollowComp = Controller->FindComponentByClass<UPathFollowingComponent>(); if (PFollowComp == nullptr ) { SRWARNING (TEXT ("SimpleMoveToForAllController [no UPathFollowingComponent]" )); return ; } if (!PFollowComp->IsPathFollowingAllowed ()) { SRWARNING (TEXT ("SimpleMoveToForAllController [not IsPathFollowingAllowed()]" )); return ; } bool bAlreadyAtGoal = false ; if (Target) { bAlreadyAtGoal = PFollowComp->HasReached (*Target, EPathFollowingReachMode::OverlapAgentAndGoal); } else { bAlreadyAtGoal = PFollowComp->HasReached (Destination, EPathFollowingReachMode::OverlapAgentAndGoal); } if (PFollowComp->GetStatus () != EPathFollowingStatus::Idle) { PFollowComp->AbortMove (*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest , FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep); } if (bAlreadyAtGoal) { PFollowComp->RequestMoveWithImmediateFinish (EPathFollowingResult::Success); } else { const FVector AgentNavLocation = Controller->GetNavAgentLocation (); const ANavigationData* NavData = NavSys->GetNavDataForProps (Controller->GetNavAgentPropertiesRef (), AgentNavLocation); if (NavData) { FPathFindingQuery Query (Controller, *NavData, AgentNavLocation, Target?Target->GetActorLocation():Destination) ; FPathFindingResult Result = NavSys->FindPathSync (Query); if (Result.IsSuccessful ()) { Result.Path->SetGoalActorObservation (*Target, AcceptanceRadius); FAIMoveRequest MoveReq; MoveReq.SetUsePathfinding (true ); MoveReq.SetAcceptanceRadius (AcceptanceRadius); MoveReq.SetReachTestIncludesAgentRadius (StopOnOverlap); if (Target) { MoveReq.SetGoalActor (Target); } else { MoveReq.SetGoalLocation (Destination); } PFollowComp->RequestMove (MoveReq, Result.Path); } else if (PFollowComp->GetStatus () != EPathFollowingStatus::Idle) { PFollowComp->RequestMoveWithImmediateFinish (EPathFollowingResult::Invalid); } } } }