• 我不去想是否能够成功
    既然选择了远方 便只顾风雨兼程

    我不去想能否赢得爱情
    既然钟情于玫瑰 就勇敢地吐露真诚

    我不去想身后会不会袭来寒风冷雨
    既然目标是地平线 留给世界的只能是背影

  • 不明白你们遇到好事,为什么要掐腿揉眼睛,真醒了怎么办?

我能想到最浪漫的事,就是和你一起吃吃吃,然后你付钱。

UE4自定义NavLink实现指南

# UE4 自定义 NavLink 实现指南

## 概述

本文档详细介绍了如何在 UE4 中实现自定义的 NavLink 系统,包括自定义 NavLink 组件、路径跟随组件和 NavLinkProxy 的完整实现方案。该系统主要用于 Test 功能,支持 AI 在导航过程中与游戏对象(如门)进行交互。

## 系统架构

自定义 NavLink 系统由三个核心组件构成:

1. **UNavLinkTestCustomComponent** - 自定义 NavLink 组件
2. **UTestPathFollowingComponent** - 自定义路径跟随组件
3. **ATestNavLinkProxy** - NavLink 代理 Actor

### 架构图

```
┌─────────────────────────────────────┐
│  ATestMonsterAIController           │
│  (AI 控制器)                         │
│  - 使用自定义 PathFollowingComponent │
└──────────────┬──────────────────────┘
               │
               │ 使用
               ▼
┌─────────────────────────────────────┐
│  UTestPathFollowingComponent        │
│  (路径跟随组件)                      │
│  - NavLink 前停顿                    │
│  - 触发 NavLink 事件                 │
└──────────────┬──────────────────────┘
               │
               │ 交互
               ▼
┌─────────────────────────────────────┐
│  ATestNavLinkProxy                  │
│  (NavLink 代理)                      │
│  - 包含自定义 NavLink 组件           │
└──────────────┬──────────────────────┘
               │
               │ 包含
               ▼
┌─────────────────────────────────────┐
│  UNavLinkTestCustomComponent        │
│  (自定义 NavLink 组件)               │
│  - 控制通行权限                      │
│  - 触发门开关                        │
└─────────────────────────────────────┘
```

## 组件详解

### 1. UNavLinkTestCustomComponent

自定义 NavLink 组件继承自 `UNavLinkCustomComponent`,提供了扩展的导航链接功能。

#### 主要特性

- **通行权限控制**:支持分别控制怪物和玩家的通行权限
- **GM 指令支持**:通过控制台变量动态控制 NavLink 的启用状态
- **GPO Actor 关联**:可以关联游戏对象(如门),在 AI 通过时触发交互
- **蓝图扩展**:支持通过蓝图实现自定义的寻路权限判断

#### 头文件定义

```cpp
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. 寻路权限判断

```cpp
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 &amp;&amp; Super::IsLinkPathfindingAllowed(Querier);
}
```

##### 2. NavLink 移动事件处理

```cpp
bool UNavLinkTestCustomComponent::OnLinkMoveStarted(UObject* PathComp, const FVector&amp; 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 信息,停顿结束后继续移动

#### 头文件定义

```cpp
UCLASS()
class FEATURE_Test_API UTestPathFollowingComponent : public UPathFollowingComponent
{
    GENERATED_BODY()

public:
    UTestPathFollowingComponent(const FObjectInitializer&amp; 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&amp; DestPoint) override;

    /** NavLink 停顿结束后继续移动 */
    void OnNavLinkPauseFinished();

    /** 重写 UpdatePathSegment,处理 NavLink 停顿 */
    virtual void UpdatePathSegment() override;
    virtual void FollowPathSegment(float DeltaTime) override;

    /** 清理定时器 */
    virtual void Reset() override;
};
```

#### 核心实现

##### 1. 开始使用 NavLink

```cpp
void UTestPathFollowingComponent::StartUsingCustomLink(INavLinkCustomInterface* CustomNavLink, const FVector&amp; DestPoint)
{
    // 如果设置了停顿时间,则先停顿
    if (NavLinkPauseTime > 0.0f &amp;&amp; !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,
                &amp;UTestPathFollowingComponent::OnNavLinkPauseFinished,
                NavLinkPauseTime,
                false
            );
        }
    }
    else
    {
        // 没有设置停顿时间,直接调用父类方法
        Super::StartUsingCustomLink(CustomNavLink, DestPoint);
    }
}
```

##### 2. 停顿结束处理

```cpp
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. 路径更新控制

```cpp
void UTestPathFollowingComponent::UpdatePathSegment()
{
    // 如果正在等待 NavLink 停顿,不更新路径段
    if (bWaitingForNavLinkPause)
    {
        return;
    }
    Super::UpdatePathSegment();
}

void UTestPathFollowingComponent::FollowPathSegment(float DeltaTime)
{
    // 如果正在等待 NavLink 停顿,不更新路径段
    if (bWaitingForNavLinkPause)
    {
        return;
    }
    Super::FollowPathSegment(DeltaTime);
}
```

##### 4. 资源清理

```cpp
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 关联**:支持关联游戏对象并根据距离选择最近的对象

#### 头文件定义

```cpp
UCLASS(Blueprintable, autoCollapseCategories = (SmartLink, Actor), hideCategories = (Input))
class ATestNavLinkProxy : public ANavLinkProxy, public IUnLuaInterface
{
    GENERATED_BODY()

public:
    ATestNavLinkProxy(const FObjectInitializer&amp; ObjectInitializer);

    // 设置 NavLink 数据
    UFUNCTION(BlueprintCallable, Category = "Test")
    void SetupSmartLinkData(const FVector&amp; Start, const FVector&amp; 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. 构造函数 - 替换默认组件

```cpp
ATestNavLinkProxy::ATestNavLinkProxy(const FObjectInitializer&amp; 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 数据

```cpp
void ATestNavLinkProxy::SetupSmartLinkData(const FVector&amp; Start, const FVector&amp; 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 关联(智能距离选择)

```cpp
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 控制器中使用自定义路径跟随组件:

```cpp
UCLASS()
class FEATURE_Test_API ATestMonsterAIController : public AMoeMonsterAIController
{
    GENERATED_BODY()

public:
    ATestMonsterAIController(const FObjectInitializer&amp; ObjectInitializer);
};

// 实现文件
ATestMonsterAIController::ATestMonsterAIController(const FObjectInitializer&amp; ObjectInitializer)
    : Super(ObjectInitializer.SetDefaultSubobjectClass<UTestPathFollowingComponent>(TEXT("PathFollowingComponent")))
{
    // 通过 ObjectInitializer 替换默认的 PathFollowingComponent
}
```

## 使用流程

### 1. 在关卡中放置 NavLinkProxy

```cpp
// 在蓝图或 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 控制器

```cpp
// AI 控制器会自动使用 UTestPathFollowingComponent
ATestMonsterAIController* AIController = Cast<ATestMonsterAIController>(Monster->GetController());

// 可选:设置 NavLink 停顿时间
if (UTestPathFollowingComponent* PathFollowComp = Cast<UTestPathFollowingComponent>(AIController->GetPathFollowingComponent()))
{
    PathFollowComp->SetNavLinkPauseTime(3.0f); // 停顿 3 秒
}
```

### 3. AI 寻路

```cpp
// 正常使用 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` 替换默认的子对象类:

```cpp
// 在 AI 控制器中替换 PathFollowingComponent
ATestMonsterAIController::ATestMonsterAIController(const FObjectInitializer&amp; ObjectInitializer)
    : Super(ObjectInitializer.SetDefaultSubobjectClass<UTestPathFollowingComponent>(TEXT("PathFollowingComponent")))
{
}

// 在 NavLinkProxy 中替换 SmartLinkComp
ATestNavLinkProxy::ATestNavLinkProxy(const FObjectInitializer&amp; ObjectInitializer)
    : Super(ObjectInitializer.SetDefaultSubobjectClass<UNavLinkTestCustomComponent>(TEXT("SmartLinkComp")))
{
}
```

### 2. 弱指针避免循环引用

使用 `TWeakObjectPtr` 和 `TWeakInterfacePtr` 避免循环引用:

```cpp
// 存储 GPO Actor 的弱指针
TWeakObjectPtr<ATestInteractiveActorBase> RelativeGPOActor;

// 存储 NavLink 接口的弱指针
TWeakInterfacePtr<INavLinkCustomInterface> CachedCustomNavLink;
```

### 3. 定时器管理

使用 UE4 的定时器系统实现延迟执行:

```cpp
// 设置定时器
AIOwner->GetWorldTimerManager().SetTimer(
    NavLinkPauseTimerHandle,
    this,
    &amp;UTestPathFollowingComponent::OnNavLinkPauseFinished,
    NavLinkPauseTime,
    false
);

// 清理定时器
AIOwner->GetWorldTimerManager().ClearTimer(NavLinkPauseTimerHandle);
NavLinkPauseTimerHandle.Invalidate();
```

### 4. 控制台变量(CVar)

使用控制台变量实现运行时调试:

```cpp
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 的可视化日志系统:

```cpp
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 系统的完整实现方案,包括:

1. **三个核心组件**:NavLink 组件、路径跟随组件和 NavLinkProxy
2. **关键技术**:组件替换、弱指针、定时器、控制台变量
3. **完整流程**:从 AI 寻路到 NavLink 使用的完整工作流程
4. **最佳实践**:内存管理、性能优化、扩展性设计

通过这套系统,可以实现 AI 在导航过程中与游戏对象的智能交互,例如自动开门、等待障碍物移除等功能。系统设计灵活,易于扩展,适合在各种 PVE 场景中使用。

## 参考资料

- UE4 官方文档:Navigation System
- UE4 官方文档:AI Navigation
- UE4 源码:`NavLinkCustomComponent.h`
- UE4 源码:`PathFollowingComponent.h`
标签

最新评论