42,将移向指定位置改为c++

蓝图逻辑的精准还原(1:1 映射)

核心节点还原:将 AI MoveTo 节点还原为 FAIMoveRequest + AIC->MoveTo()。

严格保留参数:Acceptance Radius = 20.0,SetTimer 的 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. 构建 FAIMoveRequest(UE5.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
    );
}

}

相关推荐
ue星空3 天前
UE5放了playstart显示badsize解决方案
ue5
directx3d_beginner5 天前
41,从UE5.1迁移到UE5.8排坑全记录
ue5
落魄大学生之流水线上谋生计8 天前
BLACK TIDE · 黑潮
ue5
曼巴UE516 天前
UE5 客户端 需要的网络同步概念总结(3 )-事件同步 RPC 以及三种调用方式
网络·c++·网络协议·学习·rpc·ue5
归真仙人19 天前
【UE项目迁移银河麒麟(Linux)局域网会话搜索失败排查】
ue5·ue4·vr·unreal engine·银河麒麟
fanfan_hongyun20 天前
UE5.1 应用InWebSocketClient-main 接收websocket信息
ue5
远离UE421 天前
Chaos、WFC 与 FJA 原理与区别笔记
ue5
平行云21 天前
3D应用推流太贵太慢?ImmerShare Lite:利用本地算力实现极简一键分享
unity·ue5·实时云渲染·云桌面·像素流·云游戏·串流
fanfan_hongyun22 天前
UE5.1 VaRest插件 读取json http请求
ue5