
蓝图逻辑的精准还原1:1 映射核心节点还原将 AI MoveTo 节点还原为 FAIMoveRequest AIC-MoveTo()。严格保留参数Acceptance Radius 20.0SetTimer 的 Time 0.2s 且 Looping true。Do Once 机制还原确认了蓝图中 Do Once 有输入、输出且 Reset 有连线。在 C 中转化为 bLocationRetryStarted 变量在 HandleMoveToLocationSuccess 中重置为 false。对象池定时器保留了蓝图中的 TimerHandle_ReturnToPool 清除逻辑。二、 UE C 架构设计职责分离摒弃了把逻辑全塞进一个函数的做法拆分为清晰的四层架构入口层 (MoveToLocation)接收目标参数更新缓存 CachedLocation。核心执行层 (ExecuteMoveToLocation)构建 FAIMoveRequest处理同步返回值绑定异步回调。定时器也直接绑定此函数天然无参完美适配。回调层 (OnMoveToLocationCompleted)处理引擎底层的异步 PathFollowingComponent 结果包含 RequestID 防串号过滤。结果处理层 (HandleMoveToLocationSuccess / Fail)将同步AlreadyAtGoal/Invalid和异步Success/Fail的结果统一汇聚避免代码重复。失败时触发 0.2 秒循环定时器。三、 核心技术避坑指南同步 vs 异步的区分明确了 MoveTo() 的同步返回值请求是否发出和 OnMoveCompleted 的异步回调是否到达/中途失败是不同的阶段解答了“为什么两处都可能成功失败却调用相同函数”的疑惑。RequestID 过滤确认了在高频切换目标时必须靠 CurrentMoveRequestID 对比来拦截过期回调防止旧请求的 Aborted 干扰新逻辑。UE5.8 API 适配修正了 FAIMoveRequest 中不存在 SetStopOnOverlap 的问题改用 SetReachTestIncludesAgentRadius(false) 来对应蓝图中未勾选的 Stop on Overlap。蓝图过渡期兼容给 TimerHandle 变量加上 UPROPERTY(EditAnywhere, BlueprintReadWrite) 并移到 public 区域解决了 not blueprint writable 的编译错误。代码如下public://移动向某一位置// 蓝图过渡期需要的定时器变量 // 【新增】加上 UPROPERTY 宏允许蓝图读写 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category AI|Movement|Location) FTimerHandle TimerHandle_RestartApproachingLocation; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category AI|Movement|Location) FTimerHandle TimerHandle_ReturnToPool; // 缓存目标坐标暴露给蓝图供过渡期手动赋值 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category AI|Movement|Location) FVector CachedLocation; // 核心执行者定时器绑定 蓝图过渡期调用 UFUNCTION(BlueprintCallable, Category AI|Movement|Location) void ExecuteMoveToLocation();protected:// 回调层 // 异步回调由 PathFollowingComponent 触发 void OnMoveToLocationCompleted(FAIRequestID RequestID, const FPathFollowingResult Result); // 成功处理同步 AlreadyAtGoal 和异步 Success 都会调用它 void HandleMoveToLocationSuccess(); // 失败处理同步 Invalid 和异步 Fail 都会调用它 void HandleMoveToLocationFail(); // 状态重置对应蓝图右上角 Reset Status by Timer // UFUNCTION() //void ResetStatus();private:// 对应蓝图中的 Do Once 节点bool bLocationRetryStarted false;// 当前移动请求 ID用于过滤过期回调防串号 FAIRequestID CurrentLocationMoveRequestID; // 对应蓝图中 AI MoveTo 节点的 Acceptance Radius 20.0 const float AcceptanceRadius 20.0f;#include “Enemy.h”#include Components/CapsuleComponent.h#include GameFramework/CharacterMovementComponent.h#include PaperFlipbookComponent.h#include Interface/HitHandle_Player.h#include Net/UnrealNetwork.h#include Kismet/GameplayStatics.h#include AIController.h#include AITypes.h#include Blueprint/AIAsyncTaskBlueprintProxy.h// 核心执行者 void AEnemy::ExecuteMoveToLocation(){AAIController* AIC Cast(GetController());if (!AIC) return;// 1. 构建 FAIMoveRequestUE5.8 API 适配 FAIMoveRequest MoveReq; MoveReq.SetGoalLocation(CachedLocation); MoveReq.SetAcceptanceRadius(AcceptanceRadius); // 20.0f MoveReq.SetReachTestIncludesAgentRadius(false); // 对应蓝图 Stop on Overlap 未勾选 MoveReq.SetUsePathfinding(true); MoveReq.SetAllowPartialPath(true); // 2. 执行移动 FPathFollowingRequestResult MoveResult AIC-MoveTo(MoveReq); // 3. 处理同步阶段结果 if (MoveResult.Code EPathFollowingRequestResult::RequestSuccessful) { CurrentLocationMoveRequestID MoveResult.MoveId; if (AIC-GetPathFollowingComponent()) { AIC-GetPathFollowingComponent()-OnRequestFinished.RemoveAll(this); AIC-GetPathFollowingComponent()-OnRequestFinished.AddUObject(this, AEnemy::OnMoveToLocationCompleted); } } else if (MoveResult.Code EPathFollowingRequestResult::AlreadyAtGoal) { HandleMoveToLocationSuccess(); } else { HandleMoveToLocationFail(); }}// 异步回调 void AEnemy::OnMoveToLocationCompleted(FAIRequestID RequestID, const FPathFollowingResult Result){// 防串号过滤if (RequestID.IsValid() CurrentLocationMoveRequestID.IsValid() RequestID ! CurrentLocationMoveRequestID){return;}if (Result.IsSuccess()) { HandleMoveToLocationSuccess(); } else { HandleMoveToLocationFail(); }}// 成功处理 void AEnemy::HandleMoveToLocationSuccess(){UWorld* World GetWorld();if (!World) return;FTimerManager TimerMgr World-GetTimerManager(); // 对应蓝图Clear and Invalidate Timer by Handle (两个定时器) TimerMgr.ClearTimer(TimerHandle_RestartApproachingLocation); TimerMgr.ClearTimer(TimerHandle_ReturnToPool); // 对应蓝图Do Once 的 Reset 引脚连线 bLocationRetryStarted false; // 对应蓝图Reset Status by Timer// ResetStatus();}// 失败处理 void AEnemy::HandleMoveToLocationFail(){UWorld* World GetWorld();if (!World) return;FTimerManager TimerMgr World-GetTimerManager(); // 对应蓝图Do Once 节点 (Start Closed false) if (!bLocationRetryStarted) { bLocationRetryStarted true; // 对应蓝图Set Timer by Event (Time: 0.2, Looping: true) TimerMgr.SetTimer( TimerHandle_RestartApproachingLocation, this, AEnemy::ExecuteMoveToLocation, 0.2f, true ); }}