UE4 自定义 NavLink 实现指南
概述
本文档详细介绍了如何在 UE4 中实现自定义的 NavLink 系统,包括自定义 NavLink 组件、路径跟随组件和 NavLinkProxy 的完整实现方案。该系统主要用于 Test 功能,支持 AI 在导航过程中与游戏对象(如门)进行交互。
系统架构
自定义 NavLink 系统由三个核心组件构成:
- UNavLinkTestCustomComponent – 自定义 NavLink 组件
- UTestPathFollowingComponent – 自定义路径跟随组件
- ATestNavLinkProxy – NavLink 代理 Actor
架构图
┌─────────────────────────────────────┐
│ ATestMonsterAIController │
│ (AI 控制器) │
│ - 使用自定义 PathFollowingComponent │
└──────────────┬──────────────────────┘
│
│ 使用
▼
┌─────────────────────────────────────┐
│ UTestPathFollowingComponent │
│ (路径跟随组件) │
│ - NavLink 前停顿 │
│ - 触发 NavLink 事件 │
└──────────────┬──────────────────────┘
│
│ 交互
▼
┌─────────────────────────────────────┐
│ ATestNavLinkProxy │
│ (NavLink 代理) │
│ - 包含自定义 NavLink 组件 │
└──────────────┬──────────────────────┘
│
│ 包含
▼
┌─────────────────────────────────────┐
│ UNavLinkTestCustomComponent │
│ (自定义 NavLink 组件) │
│ - 控制通行权限 │
│ - 触发门开关 │
└─────────────────────────────────────┘
组件详解
1. UNavLinkTestCustomComponent
自定义 NavLink 组件继承自 UNavLinkCustomComponent,提供了扩展的导航链接功能。
主要特性
- 通行权限控制:支持分别控制怪物和玩家的通行权限
- GM 指令支持:通过控制台变量动态控制 NavLink 的启用状态
- GPO Actor 关联:可以关联游戏对象(如门),在 AI 通过时触发交互
- 蓝图扩展:支持通过蓝图实现自定义的寻路权限判断
头文件定义
UCLASS(ClassGroup = (Navigation), meta = (BlueprintSpawnableComponent))
class UNavLinkTestCustomComponent : public UNavLinkCustomComponent, public IUnLuaInterface
{
GENERATED_BODY()
public:
UNavLinkTestCustomComponent(const FObjectInitializer& ObjectInitializer);
// 蓝图可实现的寻路权限判断
UFUNCTION(BlueprintImplementableEvent, Category = "Test|NavLink")
bool IsLinkPathfindingAllowedBP(const UObject* Querier) const;
protected:
// 重写父类虚函数
virtual bool IsLinkPathfindingAllowed(const UObject* Querier) const override;
virtual bool OnLinkMoveStarted(UObject* PathComp, const FVector& DestPoint) override;
virtual void OnLinkMoveFinished(UObject* PathComp) override;
public:
// 通行权限控制
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test|NavLink")
bool bAllowMonsterPass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Test|NavLink")
bool bAllowPlayerPass;
// GPO Actor 管理
UFUNCTION(BlueprintCallable, Category = "Test|NavLink")
void SetRelativeGPOActor(AActor* InGPOActor);
UFUNCTION(BlueprintCallable, Category = "Test|NavLink")
ATestInteractiveActorBase* GetRelativeGPOActor() const;
protected:
TWeakObjectPtr<ATestInteractiveActorBase> RelativeGPOActor;
};
核心实现
1. 寻路权限判断
bool UNavLinkTestCustomComponent::IsLinkPathfindingAllowed(const UObject* Querier) const
{
// 检查是否是怪物 AI 控制器
const ATestMonsterAIController* AIController = Cast<ATestMonsterAIController>(Querier);
if (AIController)
{
// 首先检查 GM 指令(控制台变量)
const int32 bGMAllowMonster = CVarTestNavLinkAllowMonster.GetValueOnGameThread();
if (bGMAllowMonster == 0)
{
return false;
}
// 然后检查组件自身的属性
return bAllowMonsterPass;
}
// 检查玩家权限
const int32 bGMAllowPlayer = CVarTestNavLinkAllowPlayer.GetValueOnGameThread();
if (bGMAllowPlayer == 0)
{
return false;
}
return bAllowPlayerPass && Super::IsLinkPathfindingAllowed(Querier);
}
2. NavLink 移动事件处理
bool UNavLinkTestCustomComponent::OnLinkMoveStarted(UObject* PathComp, const FVector& DestPoint)
{
// 当 AI 开始使用 NavLink 时,打开关联的门
if (RelativeGPOActor.IsValid())
{
auto PathFollowCmp = Cast<UTestPathFollowingComponent>(PathComp);
if (PathFollowCmp)
{
RelativeGPOActor->OpenByAI(PathFollowCmp->GetOwner());
}
}
return Super::OnLinkMoveStarted(PathComp, DestPoint);
}
void UNavLinkTestCustomComponent::OnLinkMoveFinished(UObject* PathComp)
{
// 当 AI 完成 NavLink 移动时,关闭关联的门
if (RelativeGPOActor.IsValid())
{
auto PathFollowCmp = Cast<UTestPathFollowingComponent>(PathComp);
if (PathFollowCmp)
{
RelativeGPOActor->CloseByAI(PathFollowCmp->GetOwner());
}
}
Super::OnLinkMoveFinished(PathComp);
}
GM 指令
系统提供了两个控制台变量用于调试:
// 控制怪物能否使用 NavLink
Test.NavLink.AllowMonster 0/1
// 控制玩家能否使用 NavLink
Test.NavLink.AllowPlayer 0/1
2. UTestPathFollowingComponent
自定义路径跟随组件继承自 UPathFollowingComponent,主要功能是在 AI 使用 NavLink 前添加停顿时间。
主要特性
- NavLink 前停顿:在使用 NavLink 前停顿指定时间(例如等待门打开)
- 定时器管理:使用定时器控制停顿时间
- 状态缓存:缓存 NavLink 信息,停顿结束后继续移动
头文件定义
UCLASS()
class FEATURE_Test_API UTestPathFollowingComponent : public UPathFollowingComponent
{
GENERATED_BODY()
public:
UTestPathFollowingComponent(const FObjectInitializer& ObjectInitializer);
/** 设置 NavLink 停顿时间 */
void SetNavLinkPauseTime(float InPauseTime);
protected:
/** NavLink 停顿时间(秒) */
float NavLinkPauseTime;
/** 是否正在等待 NavLink 停顿 */
bool bWaitingForNavLinkPause;
/** NavLink 停顿计时器句柄 */
FTimerHandle NavLinkPauseTimerHandle;
/** 缓存的 NavLink 目标点 */
FVector CachedNavLinkDestPoint;
/** 缓存的 NavLink 接口 */
TWeakInterfacePtr<INavLinkCustomInterface> CachedCustomNavLink;
/** 重写 StartUsingCustomLink,在使用 NavLink 前停顿 */
virtual void StartUsingCustomLink(INavLinkCustomInterface* CustomNavLink, const FVector& DestPoint) override;
/** NavLink 停顿结束后继续移动 */
void OnNavLinkPauseFinished();
/** 重写 UpdatePathSegment,处理 NavLink 停顿 */
virtual void UpdatePathSegment() override;
virtual void FollowPathSegment(float DeltaTime) override;
/** 清理定时器 */
virtual void Reset() override;
};
核心实现
1. 开始使用 NavLink
void UTestPathFollowingComponent::StartUsingCustomLink(INavLinkCustomInterface* CustomNavLink, const FVector& DestPoint)
{
// 如果设置了停顿时间,则先停顿
if (NavLinkPauseTime > 0.0f && !bWaitingForNavLinkPause)
{
UE_VLOG(GetOwner(), LogPathFollowing, Log,
TEXT("PVE PathFollowing: Pausing %.2f seconds before using NavLink"),
NavLinkPauseTime);
// 缓存 NavLink 信息
CachedCustomNavLink = CustomNavLink;
CachedNavLinkDestPoint = DestPoint;
bWaitingForNavLinkPause = true;
// 触发 NavLink 开始事件
CustomNavLink->OnLinkMoveStarted(this, DestPoint);
// 暂停移动
if (MovementComp)
{
MovementComp->StopMovementKeepPathing();
}
// 设置定时器,停顿结束后继续
if (AAIController* AIOwner = Cast<AAIController>(GetOwner()))
{
AIOwner->GetWorldTimerManager().SetTimer(
NavLinkPauseTimerHandle,
this,
&UTestPathFollowingComponent::OnNavLinkPauseFinished,
NavLinkPauseTime,
false
);
}
}
else
{
// 没有设置停顿时间,直接调用父类方法
Super::StartUsingCustomLink(CustomNavLink, DestPoint);
}
}
2. 停顿结束处理
void UTestPathFollowingComponent::OnNavLinkPauseFinished()
{
UE_VLOG(GetOwner(), LogPathFollowing, Log,
TEXT("PVE PathFollowing: NavLink pause finished, continuing move"));
bWaitingForNavLinkPause = false;
NavLinkPauseTimerHandle.Invalidate();
// 继续使用 NavLink
if (CachedCustomNavLink.IsValid())
{
INavLinkCustomInterface* CustomNavLink = CachedCustomNavLink.GetInterface();
Super::StartUsingCustomLink(CustomNavLink, CachedNavLinkDestPoint);
// 清理缓存
CachedCustomNavLink = TWeakInterfacePtr<INavLinkCustomInterface>();
CachedNavLinkDestPoint = FVector::ZeroVector;
}
}
3. 路径更新控制
void UTestPathFollowingComponent::UpdatePathSegment()
{
// 如果正在等待 NavLink 停顿,不更新路径段
if (bWaitingForNavLinkPause)
{
return;
}
Super::UpdatePathSegment();
}
void UTestPathFollowingComponent::FollowPathSegment(float DeltaTime)
{
// 如果正在等待 NavLink 停顿,不更新路径段
if (bWaitingForNavLinkPause)
{
return;
}
Super::FollowPathSegment(DeltaTime);
}
4. 资源清理
void UTestPathFollowingComponent::Reset()
{
// 清理定时器
if (NavLinkPauseTimerHandle.IsValid())
{
if (AAIController* AIOwner = Cast<AAIController>(GetOwner()))
{
AIOwner->GetWorldTimerManager().ClearTimer(NavLinkPauseTimerHandle);
}
NavLinkPauseTimerHandle.Invalidate();
}
bWaitingForNavLinkPause = false;
CachedCustomNavLink = TWeakInterfacePtr<INavLinkCustomInterface>();
CachedNavLinkDestPoint = FVector::ZeroVector;
Super::Reset();
}
3. ATestNavLinkProxy
NavLink 代理 Actor 继承自 ANavLinkProxy,用于在关卡中放置和配置 NavLink。
主要特性
- 自定义组件替换:通过
ObjectInitializer替换默认的UNavLinkCustomComponent - 动态配置:支持运行时设置 NavLink 的起点、终点和方向
- GPO Actor 关联:支持关联游戏对象并根据距离选择最近的对象
头文件定义
UCLASS(Blueprintable, autoCollapseCategories = (SmartLink, Actor), hideCategories = (Input))
class ATestNavLinkProxy : public ANavLinkProxy, public IUnLuaInterface
{
GENERATED_BODY()
public:
ATestNavLinkProxy(const FObjectInitializer& ObjectInitializer);
// 设置 NavLink 数据
UFUNCTION(BlueprintCallable, Category = "Test")
void SetupSmartLinkData(const FVector& Start, const FVector& End, ENavLinkDirection::Type Direction);
// 获取起点和终点
UFUNCTION(BlueprintCallable, Category = "Test")
FVector GetStartPoint();
UFUNCTION(BlueprintCallable, Category = "Test")
FVector GetEndPoint();
// GPO Actor 管理
UFUNCTION(BlueprintCallable, Category = "Test")
void SetRelativeGPOActor(AActor* InGPOActor, bool Force = false);
UFUNCTION(BlueprintCallable, Category = "Test")
AActor* GetRelativeGPOActor() const;
// 通行权限控制
UFUNCTION(BlueprintCallable, Category = "Test")
bool IsAllowMonsterPass() const;
UFUNCTION(BlueprintCallable, Category = "Test")
void SetAllowMonsterPass(bool bInAllowMonsterPass);
};
核心实现
1. 构造函数 – 替换默认组件
ATestNavLinkProxy::ATestNavLinkProxy(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UNavLinkTestCustomComponent>(TEXT("SmartLinkComp")))
{
// 构造函数中通过 ObjectInitializer 替换默认的 SmartLinkComp 为自定义组件
// 这样 GetSmartLinkComp() 返回的就是 UNavLinkTestCustomComponent 实例
UNavLinkCustomComponent* SmartLinkComponent = GetSmartLinkComp();
if (!SmartLinkComponent)
{
return;
}
uint32 UserId = SmartLinkComponent->GetLinkId();
if (PointLinks.Num() > 0)
{
PointLinks[0].UserId = SmartLinkComponent->GetLinkId();
}
}
2. 设置 NavLink 数据
void ATestNavLinkProxy::SetupSmartLinkData(const FVector& Start, const FVector& End, ENavLinkDirection::Type Direction)
{
UNavLinkCustomComponent* SmartLinkComponent = GetSmartLinkComp();
if (!SmartLinkComponent)
{
return;
}
uint32 UserId = SmartLinkComponent->GetLinkId();
if (UserId == 0)
{
UE_LOG(LogTemp, Error, TEXT("TestNavLinkProxy's UserId is 0!!!"));
}
// 设置 PointLinks 数据
if (PointLinks.Num() > 0)
{
PointLinks[0].Left = Start;
PointLinks[0].Right = End;
PointLinks[0].Direction = Direction;
PointLinks[0].UserId = SmartLinkComponent->GetLinkId();
}
// 设置组件数据
if (SmartLinkComponent)
{
SmartLinkComponent->SetLinkData(Start, End, Direction);
SmartLinkComponent->SetNavigationRelevancy(true);
}
}
3. GPO Actor 关联(智能距离选择)
void ATestNavLinkProxy::SetRelativeGPOActor(AActor* InGPOActor, bool Force)
{
UNavLinkTestCustomComponent* SmartLinkComponent = Cast<UNavLinkTestCustomComponent>(GetSmartLinkComp());
if (SmartLinkComponent)
{
if (Force)
{
SmartLinkComponent->SetRelativeGPOActor(InGPOActor);
return;
}
// 如果已经设置了 GPOActor,比较距离,选择更近的
auto RelativeGPOActor = SmartLinkComponent->GetRelativeGPOActor();
if (RelativeGPOActor)
{
float Distance = (RelativeGPOActor->GetActorLocation() - this->GetActorLocation()).Size();
float NewDistance = (InGPOActor->GetActorLocation() - this->GetActorLocation()).Size();
if (NewDistance < Distance)
{
SmartLinkComponent->SetRelativeGPOActor(InGPOActor);
}
return;
}
SmartLinkComponent->SetRelativeGPOActor(InGPOActor);
}
}
4. AI 控制器集成
在 AI 控制器中使用自定义路径跟随组件:
UCLASS()
class FEATURE_Test_API ATestMonsterAIController : public AMoeMonsterAIController
{
GENERATED_BODY()
public:
ATestMonsterAIController(const FObjectInitializer& ObjectInitializer);
};
// 实现文件
ATestMonsterAIController::ATestMonsterAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UTestPathFollowingComponent>(TEXT("PathFollowingComponent")))
{
// 通过 ObjectInitializer 替换默认的 PathFollowingComponent
}
使用流程
1. 在关卡中放置 NavLinkProxy
// 在蓝图或 C++ 中创建 TestNavLinkProxy
ATestNavLinkProxy* NavLinkProxy = World->SpawnActor<ATestNavLinkProxy>(Location, Rotation);
// 设置 NavLink 的起点和终点
FVector StartPoint = FVector(0, 0, 0);
FVector EndPoint = FVector(0, 0, 100);
NavLinkProxy->SetupSmartLinkData(StartPoint, EndPoint, ENavLinkDirection::BothWays);
// 关联门对象
NavLinkProxy->SetRelativeGPOActor(DoorActor);
// 设置通行权限
NavLinkProxy->SetAllowMonsterPass(true);
2. 配置 AI 控制器
// AI 控制器会自动使用 UTestPathFollowingComponent
ATestMonsterAIController* AIController = Cast<ATestMonsterAIController>(Monster->GetController());
// 可选:设置 NavLink 停顿时间
if (UTestPathFollowingComponent* PathFollowComp = Cast<UTestPathFollowingComponent>(AIController->GetPathFollowingComponent()))
{
PathFollowComp->SetNavLinkPauseTime(3.0f); // 停顿 3 秒
}
3. AI 寻路
// 正常使用 AI 寻路
AIController->MoveToLocation(TargetLocation);
// 当 AI 遇到 NavLink 时:
// 1. PathFollowingComponent 检测到 NavLink
// 2. 调用 NavLinkComponent->IsLinkPathfindingAllowed() 检查权限
// 3. 如果允许,调用 StartUsingCustomLink()
// 4. NavLinkComponent->OnLinkMoveStarted() 被调用,触发门打开
// 5. PathFollowingComponent 停顿指定时间
// 6. 停顿结束后,AI 继续移动通过 NavLink
// 7. NavLinkComponent->OnLinkMoveFinished() 被调用,触发门关闭
工作流程图
┌─────────────────────┐
│ AI 开始寻路 │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 检测到 NavLink │
└──────────┬──────────┘
│
▼
┌─────────────────────────────────┐
│ IsLinkPathfindingAllowed() │
│ - 检查 GM 指令 │
│ - 检查通行权限 │
└──────────┬──────────────────────┘
│
▼ (允许通过)
┌─────────────────────────────────┐
│ StartUsingCustomLink() │
│ - 缓存 NavLink 信息 │
│ - 触发 OnLinkMoveStarted() │
│ - 停止移动 │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ OnLinkMoveStarted() │
│ - 打开关联的门 │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 等待停顿时间 │
│ (定时器) │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ OnNavLinkPauseFinished() │
│ - 继续使用 NavLink │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ AI 移动通过 NavLink │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────────────────┐
│ OnLinkMoveFinished() │
│ - 关闭关联的门 │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────┐
│ 继续寻路 │
└─────────────────────┘
关键技术点
1. ObjectInitializer 替换默认组件
UE4 允许在构造函数中通过 ObjectInitializer 替换默认的子对象类:
// 在 AI 控制器中替换 PathFollowingComponent
ATestMonsterAIController::ATestMonsterAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UTestPathFollowingComponent>(TEXT("PathFollowingComponent")))
{
}
// 在 NavLinkProxy 中替换 SmartLinkComp
ATestNavLinkProxy::ATestNavLinkProxy(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UNavLinkTestCustomComponent>(TEXT("SmartLinkComp")))
{
}
2. 弱指针避免循环引用
使用 TWeakObjectPtr 和 TWeakInterfacePtr 避免循环引用:
// 存储 GPO Actor 的弱指针
TWeakObjectPtr<ATestInteractiveActorBase> RelativeGPOActor;
// 存储 NavLink 接口的弱指针
TWeakInterfacePtr<INavLinkCustomInterface> CachedCustomNavLink;
3. 定时器管理
使用 UE4 的定时器系统实现延迟执行:
// 设置定时器
AIOwner->GetWorldTimerManager().SetTimer(
NavLinkPauseTimerHandle,
this,
&UTestPathFollowingComponent::OnNavLinkPauseFinished,
NavLinkPauseTime,
false
);
// 清理定时器
AIOwner->GetWorldTimerManager().ClearTimer(NavLinkPauseTimerHandle);
NavLinkPauseTimerHandle.Invalidate();
4. 控制台变量(CVar)
使用控制台变量实现运行时调试:
static TAutoConsoleVariable<int32> CVarTestNavLinkAllowMonster(
TEXT("Test.NavLink.AllowMonster"),
1,
TEXT("控制怪物 AI 能否使用 NavLink 进行寻路\n")
TEXT("0: 禁止怪物使用 NavLink\n")
TEXT("1: 允许怪物使用 NavLink (默认)"),
ECVF_Cheat
);
// 使用
const int32 bGMAllowMonster = CVarTestNavLinkAllowMonster.GetValueOnGameThread();
调试技巧
1. 可视化日志
使用 UE4 的可视化日志系统:
UE_VLOG(GetOwner(), LogPathFollowing, Log,
TEXT("PVE PathFollowing: Pausing %.2f seconds before using NavLink"),
NavLinkPauseTime);
2. 控制台命令
// 禁止怪物使用 NavLink
Test.NavLink.AllowMonster 0
// 允许怪物使用 NavLink
Test.NavLink.AllowMonster 1
// 显示导航网格
show Navigation
3. 断点调试
关键断点位置:
IsLinkPathfindingAllowed()– 检查寻路权限StartUsingCustomLink()– NavLink 开始使用OnLinkMoveStarted()– NavLink 移动开始OnNavLinkPauseFinished()– 停顿结束
最佳实践
1. 内存管理
- 使用弱指针(
TWeakObjectPtr)存储 Actor 引用,避免循环引用 - 在
Reset()方法中清理所有定时器和缓存数据 - 使用
IsValid()检查弱指针的有效性
2. 性能优化
- 避免在
IsLinkPathfindingAllowed()中执行耗时操作 - 使用控制台变量进行全局控制,减少单个 NavLink 的检查开销
- 合理设置 NavLink 停顿时间,避免 AI 等待过久
3. 扩展性设计
- 通过继承
UNavLinkCustomComponent实现特定功能 - 使用蓝图可实现事件(
BlueprintImplementableEvent)提供扩展点 - 支持 Lua 脚本扩展(通过
IUnLuaInterface)
4. 错误处理
- 检查组件指针的有效性
- 验证 UserId 不为 0
- 使用日志记录关键错误信息
常见问题
Q1: AI 不使用 NavLink
可能原因:
IsLinkPathfindingAllowed()返回 false- GM 指令禁用了 NavLink
- NavLink 的 UserId 为 0
- NavLink 未正确注册到导航系统
解决方案:
- 检查控制台变量设置
- 验证
bAllowMonsterPass属性 - 确保调用了
SetNavigationRelevancy(true)
Q2: AI 在 NavLink 处卡住
可能原因:
- 定时器未正确触发
bWaitingForNavLinkPause状态未正确重置- NavLink 的终点位置不可达
解决方案:
- 检查定时器是否正确设置和清理
- 在
Reset()中确保状态重置 - 验证 NavLink 的起点和终点位置
Q3: 门没有打开/关闭
可能原因:
- GPO Actor 未正确关联
OnLinkMoveStarted/Finished()未被调用- GPO Actor 的
OpenByAI/CloseByAI()方法未实现
解决方案:
- 使用
SetRelativeGPOActor()正确关联门对象 - 添加日志验证事件调用
- 检查 GPO Actor 的实现
总结
本文档详细介绍了 UE4 自定义 NavLink 系统的完整实现方案,包括:
- 三个核心组件:NavLink 组件、路径跟随组件和 NavLinkProxy
- 关键技术:组件替换、弱指针、定时器、控制台变量
- 完整流程:从 AI 寻路到 NavLink 使用的完整工作流程
- 最佳实践:内存管理、性能优化、扩展性设计
通过这套系统,可以实现 AI 在导航过程中与游戏对象的智能交互,例如自动开门、等待障碍物移除等功能。系统设计灵活,易于扩展,适合在各种 PVE 场景中使用。
参考资料
- UE4 官方文档:Navigation System
- UE4 官方文档:AI Navigation
- UE4 源码:
NavLinkCustomComponent.h - UE4 源码:
PathFollowingComponent.h