跳转至

Bug 记录

Bug 1: Static Mesh vs Actor 混淆

问题: 将 Static Mesh (SM_StationRecharge) 直接拖入场景,而不是 AMAChargingStation Actor。

现象: 场景中有充电站模型,但没有任何充电逻辑触发,没有 Overlap 事件。

原因: Static Mesh 只是模型资产,没有 C++ 类的逻辑(Overlap 检测、充电功能等)。

解决: - C++ Actor 类需要通过 Place Actors 面板搜索并拖入场景 - 或者创建 Blueprint 子类(如 BP_ChargingStation)后拖入场景 - 不能直接拖 Static Mesh 到场景

教训: UE 中 Static Mesh 和 Actor 是不同概念: - Static Mesh: 纯模型资产,无逻辑 - Actor: 可包含组件、逻辑、事件的游戏对象


Bug 2: Gameplay Tags 未注册

问题: 新增的 Gameplay Tags(如 Ability.Charge, Status.Charging)报错 "was not found"。

现象:

Error: Requested Gameplay Tag Ability.Charge was not found, tags must be loaded from config or registered as a native tag

原因: 新增的 Tags 没有在配置文件中注册。

解决: 创建 Config/DefaultGameplayTags.ini 文件,添加所有需要的 Tags:

+GameplayTagList=(Tag="Ability.Charge",DevComment="")
+GameplayTagList=(Tag="Status.Charging",DevComment="")
+GameplayTagList=(Tag="Event.Charge.Complete",DevComment="")

教训: 新增 Gameplay Tags 时,必须同时在配置文件中注册,否则运行时会报错。


Bug 3: Patrol Task 路径点循环卡住

问题: RobotDog 到达第一个路径点后,无法前进到下一个路径点,陷入无限循环。

现象:

[Navigate] RobotDog_4 already at goal
[STTask_Patrol] RobotDog_4 moving to WP[1/4] (1240, 1900)
[Navigate] RobotDog_4 already at goal
[STTask_Patrol] RobotDog_4 moving to WP[1/4] (1240, 1900)
... (无限重复)

日志分析: - ✅ StateTree 正常启动 (RunStatus: Running) - ✅ 找到了 PatrolPath (4个路径点) - ✅ 开始移动到第一个路径点 WP[1/4] (1240, 1900) - ❌ 到达后一直重复移动到同一个点,没有进入下一个路径点

根本原因: Patrol Task 的 Tick 逻辑中,状态机转换有问题:

Character->bIsMoving 变成 false 时(到达目标),代码应该: 1. 设置 Data.bIsMoving = false 2. 进入等待状态 (Data.bIsWaiting = true) 3. 等待结束后,移动到下一个路径点 (Data.CurrentWaypointIndex++)

但实际上代码一直重复调用移动到同一个路径点,没有正确处理"到达路径点"的状态转换。

解决: 修复 MASTTask_Patrol.cpp 中的 Tick 函数,确保: 1. 检测到 !Character->bIsMoving && Data.bIsMoving == true 时,正确设置 Data.bIsMoving = false 2. 进入等待状态或直接递增 CurrentWaypointIndex 3. 避免在同一帧内重复发起移动请求

教训: StateTree Task 的状态机逻辑需要仔细处理状态转换边界条件,特别是"完成"状态的检测和后续处理。


Bug 4: StateTree 无法正常加载/运行

问题: StateTree 配置完成后,运行时没有任何反应,状态机不执行。

可能原因及解决方案:

  1. StateTree Asset 未分配
  2. 检查 StateTreeComponentStateTree 属性是否设置
  3. 如果用 C++ 加载,确认路径正确

  4. 缺少 AIController

  5. StateTree AI Component Schema 需要 AIController
  6. 确保 Character 设置了 AIControllerClassAutoPossessAI

    AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
    AIControllerClass = AAIController::StaticClass();
    

  7. Schema 配置错误

  8. StateTree Asset 的 Schema 必须选择 StateTree AI Component Schema
  9. 不是 StateTree Component Schema(这个不需要 AIController 但功能不同)

  10. StateTree 结构不完整

  11. 必须有 Root 状态和至少一个子状态
  12. 子状态需要有 Task 或 Transition

  13. 编译问题

  14. 自定义 Task/Condition 代码有错误会导致整个 StateTree 无法运行
  15. 检查 Output Log 是否有编译错误

教训: StateTree 不工作时,按以下顺序排查:Asset 分配 → AIController → Schema → 结构完整性 → 编译错误。


Bug 5: PatrolPath 找不到 (Actor Tag 查找失败)

问题: Patrol Task 运行时报错 "No PatrolPath found with tag 'PatrolPath'"。

现象:

[STTask_Patrol] ========== EnterState called ==========
[STTask_Patrol] No PatrolPath found with tag 'PatrolPath'

原因: 场景中的 MAPatrolPath Actor 没有设置正确的 Actor Tag。

解决: 1. 在场景中选中 MAPatrolPath Actor 2. 在 Details 面板找到 ActorTags 部分 3. 点击 + 添加 Tag: PatrolPath 4. 确保 Tag 名称与代码中查找的名称完全一致(区分大小写)

代码中的查找逻辑:

TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsWithTag(GetWorld(), FName("PatrolPath"), FoundActors);

教训: 使用 Actor Tag 进行运行时查找时,必须确保: - 场景中的 Actor 已添加对应 Tag - Tag 名称完全匹配(大小写敏感) - 如果有多个同类 Actor,考虑使用不同 Tag 区分


Bug 6: 软引用路径问题

问题: 硬编码的资产路径在资产移动/重命名后会失效。

解决: 使用 TSoftObjectPtr 软引用,让 UE 自动追踪资产路径变化:

UPROPERTY(EditAnywhere)
TSoftObjectPtr<UStaticMesh> StationMeshAsset;

教训: 避免在 C++ 中硬编码资产路径,使用软引用或在编辑器中配置。


注意: 对外使用说明请查看 功能性介绍


Bug 7: 切换命令时状态不切换(Command Tag 未完全清除)

问题: 机器人在 Follow 状态时,按 G 键发送 Patrol 命令,但机器人仍然继续 Follow,不切换到 Patrol。

现象:

[PlayerController] OnStartPatrol called (G key)
// 但没有看到 Patrol Task 启动的日志,机器人继续跟随

原因: OnStartPatrol 函数只移除了 Command.IdleCommand.Charge,没有移除 Command.Follow。StateTree 检测到 Command.Follow 仍然存在,Follow 状态的 Enter Condition 仍然满足,不会退出。

错误代码:

// 只移除了部分命令
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Idle")));
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Charge")));
ASC->AddLooseGameplayTag(PatrolCommand);

解决: 发送新命令时,必须移除所有其他命令 Tag:

// 清除所有其他命令
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Idle")));
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Patrol")));
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Charge")));
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Follow")));
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Command.Coverage")));
// 然后添加新命令
ASC->AddLooseGameplayTag(NewCommand);

教训: 使用 Gameplay Tag 作为 StateTree 状态切换条件时,发送新命令必须清除所有其他命令 Tag,否则旧状态的 Enter Condition 可能仍然满足,导致状态不切换。


Bug 8: Mass AI 编译错误 - ProcessorGroupNames 不存在

问题: Mass AI Processor 编译时报错 no member named 'ProcessorGroupNames' in namespace 'UE::Mass'

现象:

error: no member named 'ProcessorGroupNames' in namespace 'UE::Mass'; did you mean 'FProcessorGroupDesc'?
ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;

原因: UE5 的 Mass Entity 框架中不存在 UE::Mass::ProcessorGroupNames 命名空间。ExecutionOrder.ExecuteInGroup 是一个 FName 类型,可以设置为任意名称。

错误代码:

ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Movement);

解决: 使用 FName 直接指定组名:

ExecutionOrder.ExecuteInGroup = FName(TEXT("Movement"));
ExecutionOrder.ExecuteBefore.Add(FName(TEXT("Movement")));

影响文件: - MAMovementProcessor.cpp - MAEnergyDrainProcessor.cpp - MAPatrolProcessor.cpp - MAFollowProcessor.cpp - MAChargeProcessor.cpp

教训: Mass AI 的 Processor 执行顺序通过 FName 指定组名,不是使用预定义的命名空间常量。


Bug 9: Mass AI 编译错误 - FMassEntityTemplateID 类型不存在

问题: MAMassSubsystem.h 编译时报错 unknown type name 'FMassEntityTemplateID'

现象:

error: unknown type name 'FMassEntityTemplateID'
FMassEntityTemplateID RobotDogTemplateID;

原因: UE5 Mass Entity 框架中不存在 FMassEntityTemplateID 类型。创建 Entity 使用的是 FMassArchetypeHandle

错误代码:

// MAMassSubsystem.h
FMassEntityTemplateID RobotDogTemplateID;

// MAMassSubsystem.cpp
RobotDogTemplateID = EntityManager.CreateArchetype(CompositionDescriptor);
FMassEntityHandle Entity = EntityManager.CreateEntity(RobotDogTemplateID);

解决: 使用 FMassArchetypeHandle 类型:

// MAMassSubsystem.h
#include "MassArchetypeTypes.h"
FMassArchetypeHandle RobotDogArchetype;

// MAMassSubsystem.cpp
RobotDogArchetype = EntityManager.CreateArchetype(CompositionDescriptor);
FMassEntityHandle Entity = EntityManager.CreateEntity(RobotDogArchetype);

教训: Mass Entity 使用 Archetype 模式管理 Entity 模板,CreateArchetype() 返回 FMassArchetypeHandle


Bug 10: Mass AI 编译错误 - FMassEntityHandle 不支持 Blueprint

问题: MAMassSubsystem.h 中使用 FMassEntityHandle 参数的函数标记为 BlueprintCallable 时报错。

现象:

error: Type 'FMassEntityHandle' is not supported by blueprint. Function: SpawnRobotDog Parameter ReturnValue
error: Type 'TArray<FMassEntityHandle>' is not supported by blueprint. Function: GetAllRobots Parameter ReturnValue

原因: FMassEntityHandle 是 Mass Entity 框架的内部类型,不支持 Blueprint 暴露。

错误代码:

UFUNCTION(BlueprintCallable, Category = "Mass|Spawn")
FMassEntityHandle SpawnRobotDog(FVector Location);

UFUNCTION(BlueprintCallable, Category = "Mass|Query")
TArray<FMassEntityHandle> GetAllRobots() const;

解决: 移除这些函数的 UFUNCTION(BlueprintCallable) 标记,仅保留 C++ 接口:

// C++ only - FMassEntityHandle 不支持 Blueprint
FMassEntityHandle SpawnRobotDog(FVector Location);
TArray<FMassEntityHandle> GetAllRobots() const;

教训: Mass Entity 的 Handle 类型不支持 Blueprint,需要在 C++ 层面使用。如需 Blueprint 支持,需要创建包装函数或使用其他方式(如 Actor 代理)。


Bug 11: Mass StateTree 编译错误 - 基类不存在

问题: Mass StateTree Condition 使用 FMassStateTreeConditionBase 基类时报错。

现象:

error: Unable to find parent struct type for 'FMAMassSTCondition_LowEnergy' named 'FMassStateTreeConditionBase'

原因: UE5 Mass AI 框架中只有 FMassStateTreeTaskBaseFMassStateTreeEvaluatorBase,没有 FMassStateTreeConditionBase。Mass StateTree 的 Condition 使用标准的 FStateTreeConditionBase

错误代码:

#include "MassStateTreeTypes.h"

USTRUCT(meta = (DisplayName = "MA Mass Low Energy"))
struct FMAMassSTCondition_LowEnergy : public FMassStateTreeConditionBase  // 错误!

解决: 使用标准的 FStateTreeConditionBase

#include "StateTreeConditionBase.h"

USTRUCT(meta = (DisplayName = "MA Mass Low Energy"))
struct FMAMassSTCondition_LowEnergy : public FStateTreeConditionBase
{
    // 在 TestCondition 中将 Context 转换为 FMassStateTreeExecutionContext
};

教训: Mass StateTree 的 Task 使用 FMassStateTreeTaskBase,但 Condition 使用标准的 FStateTreeConditionBase,在实现中通过 static_cast 转换为 FMassStateTreeExecutionContext 来访问 Mass Entity 数据。


Bug 12: Mass AI 插件未启用

问题: 编译时警告 Mass AI 相关模块依赖未在 .uproject 中声明。

现象:

Warning: MultiAgent.uproject does not list plugin 'MassGameplay' as a dependency, but module 'MultiAgent' depends on 'MassCommon'.
Warning: MultiAgent.uproject does not list plugin 'MassAI' as a dependency, but module 'MultiAgent' depends on 'MassAIBehavior'.

原因: Build.cs 中添加了 Mass AI 模块依赖,但 .uproject 文件中没有启用对应的插件。

解决: 在 MultiAgent.uproject 的 Plugins 数组中添加:

{
    "Name": "MassEntity",
    "Enabled": true
},
{
    "Name": "MassGameplay",
    "Enabled": true
},
{
    "Name": "MassAI",
    "Enabled": true
},
{
    "Name": "StructUtils",
    "Enabled": true
}

教训: 使用 UE5 插件模块时,需要同时在 Build.cs 添加模块依赖和在 .uproject 中启用插件。


Bug 13: 编队跟随时机器人抖动 (已解决)

问题: 机器人在编队跟随 Leader 时,到达目标位置后会不断抖动。

现象: 机器人到达编队位置后,反复启动和停止移动,导致视觉上的抖动。

原因: 没有迟滞机制 (Hysteresis),机器人到达目标后立即检测到"需要移动",然后又立即到达,形成循环。

解决: 在 MAActorSubsystem 中实现迟滞机制: - StartMoveThreshold = 150.f - 距离超过此值才开始移动 - StopMoveThreshold = 80.f - 距离小于此值才停止移动 - 两个阈值的差值形成"死区",防止抖动

// 迟滞机制
if (!bIsMoving && Distance > StartMoveThreshold)
{
    // 开始移动
    bIsMoving = true;
}
else if (bIsMoving && Distance < StopMoveThreshold)
{
    // 停止移动
    bIsMoving = false;
}

教训: 实时跟随系统需要迟滞机制防止抖动,使用不同的启动/停止阈值。


Bug 14: 相机拍照图片过暗 (已解决)

问题: MACameraSensor 拍照保存的图片非常暗,几乎全黑。

现象: 调用 TakePhoto() 保存的图片亮度极低,无法看清内容。

原因: SceneCaptureComponent2D 的默认配置不正确: 1. CaptureSource 设置错误 2. Gamma 校正未正确应用 3. 后处理设置不当

解决: 参考 UnrealCV 的 LitCamSensor 实现,应用正确的相机配置:

// 使用 FinalColorLDR 作为捕获源
CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;

// 启用后处理
CaptureComponent->bCaptureEveryFrame = false;
CaptureComponent->bCaptureOnMovement = false;

// 正确的 Gamma 设置
RenderTarget->TargetGamma = 2.2f;

教训: UE5 的 SceneCaptureComponent2D 需要正确配置 CaptureSource 和 Gamma 才能获得正确的图像亮度。参考成熟项目(如 UnrealCV、CARLA)的实现。


Bug 15: GA_TakePhoto Ability 开销过大 (已解决)

问题: 使用 GAS Ability 实现拍照功能增加了不必要的复杂性。

现象: 拍照是一个简单的即时操作,但通过 GAS 实现需要: - 创建 Ability 类 - 注册到 ASC - 通过 Tag 激活 - 处理 Ability 生命周期

解决: 采用 CARLA 风格,直接在 MACameraSensor 上实现 TakePhoto() 方法:

// 直接调用,无需 GAS
AMACameraSensor* Camera = ...;
Camera->TakePhoto();  // 简单直接

清理内容: - 删除 GA_TakePhoto.h/cpp - 移除 MAAbilitySystemComponent 中的相关代码 - 更新 PlayerController 直接调用 Sensor

教训: 不是所有功能都需要通过 GAS 实现。简单的即时操作(如拍照)直接在 Actor 上实现更简洁。CARLA 风格:参数和简单操作放在实体上,复杂行为才用 Ability。


Bug 16: 相机拍照全黑 - CreateDefaultSubobject 运行时失效 (已解决)

问题: 相机组件拍照输出全黑图片,调试日志显示相机位置为 (0,0,0)。

现象:

[Camera] Human_0_Camera CaptureFrame: Location=(X=0.000 Y=0.000 Z=0.000), Rotation=(P=0.000000 Y=0.000000 R=0.000000)
[Camera] Human_0_Camera CaptureFrame: 307200 pixels, 307200 non-black (100.0%)
虽然日志显示 100% 非黑像素,但图片实际是纯色(相机在世界原点看到的内容)。

原因: CreateDefaultSubobject 只能在 CDO(Class Default Object)构造函数中使用。当运行时通过 NewObject<UMACameraSensorComponent> 动态创建组件时,构造函数中的 CreateDefaultSubobject 不会正确创建子组件(CameraComponent 和 SceneCaptureComponent),导致它们没有正确附加到父组件,位置停留在世界原点 (0,0,0)。

错误代码:

// 构造函数中使用 CreateDefaultSubobject - 只对 CDO 有效
UMACameraSensorComponent::UMACameraSensorComponent()
{
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(this);

    SceneCaptureComponent = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("SceneCaptureComponent"));
    SceneCaptureComponent->SetupAttachment(CameraComponent);
}

// 运行时动态创建 - CreateDefaultSubobject 失效!
UMACameraSensorComponent* CameraSensor = NewObject<UMACameraSensorComponent>(this, ComponentName);

解决: 将子组件创建移到 OnRegister() 中,使用 NewObject 动态创建并手动 RegisterComponent()

UMACameraSensorComponent::UMACameraSensorComponent()
{
    // 构造函数只设置默认值,不创建子组件
    SensorType = EMASensorType::Camera;
    SensorName = TEXT("Camera");
}

void UMACameraSensorComponent::OnRegister()
{
    Super::OnRegister();

    // 动态创建子组件(支持运行时 NewObject 创建)
    if (!CameraComponent)
    {
        CameraComponent = NewObject<UCameraComponent>(GetOwner(), NAME_None, RF_Transactional);
        CameraComponent->SetupAttachment(this);
        CameraComponent->SetRelativeLocation(FVector::ZeroVector);
        CameraComponent->FieldOfView = FOV;
        CameraComponent->RegisterComponent();
    }

    if (!SceneCaptureComponent)
    {
        SceneCaptureComponent = NewObject<USceneCaptureComponent2D>(GetOwner(), NAME_None, RF_Transactional);
        SceneCaptureComponent->SetupAttachment(CameraComponent);
        // ... 其他配置
        SceneCaptureComponent->RegisterComponent();
    }
}

教训: - CreateDefaultSubobject 只能在构造函数中用于 CDO 创建,运行时动态创建的对象不会执行这些调用 - 需要支持运行时动态创建的组件,应在 OnRegister() 中使用 NewObject + RegisterComponent() - 调试时检查组件的世界坐标是否正确,(0,0,0) 通常意味着附加关系有问题


Bug 17: UI 切换按键冲突 (已解决)

问题: 用户按 C 键时角色下降,没有显示 UI。

现象: 游戏中按 C 键触发角色下降动作,而不是切换 UI 显示。

原因: C 键在项目中被其他功能占用(可能是角色移动控制)。

解决: 将 UI 切换键改为 Z 键:

// MAInputActions.cpp
AddKeyMapping(DefaultMappingContext, IA_ToggleMainUI, EKeys::Z);

教训: 选择输入按键时需要避免与现有功能冲突,Z 键通常较少被占用。


Bug 18: MainWidget 为 null - 缺少输入绑定 (已解决)

问题: 按 Z 键后日志显示 MainWidget is null,UI 无法显示。

现象:

LogMAHUD: Warning: ShowMainUI: MainWidget is null
LogTemp: [PlayerController] ToggleMainUI called

原因: 1. MAPlayerController 中没有绑定 UI 切换的输入 2. MAGameMode 使用的是 MASelectionHUD 而不是 MAHUD 3. MainWidgetClass 未设置且没有 WBP_MAMainWidget 蓝图

解决: 1. 添加 IA_ToggleMainUI Input Action 并绑定到 Z 键 2. 修改 MAGameMode 使用 AMAHUD::StaticClass() 3. 创建 MASimpleMainWidget 作为纯 C++ 实现的备选方案 4. 让 MAHUD 继承自 MASelectionHUD 保留选择框功能

教训: UI 系统需要完整的输入绑定链:Input Action → PlayerController → HUD → Widget。


Bug 19: SetRootWidget 编译错误 (已解决)

问题: MASimpleMainWidget.cpp 编译时报错 use of undeclared identifier 'SetRootWidget'

现象:

error: use of undeclared identifier 'SetRootWidget'
SetRootWidget(RootBox);

原因: UE5 的 UUserWidget 没有 SetRootWidget 方法。正确的方式是使用 WidgetTree->RootWidget

解决: 使用 UE5 正确的 Widget 创建方式:

// 错误方式
SetRootWidget(RootBox);

// 正确方式
WidgetTree->RootWidget = RootCanvas;

教训: 动态创建 UMG Widget 需要使用 WidgetTree->ConstructWidget<>()WidgetTree->RootWidget


Bug 20: Widget 创建但不可见 - 锚点问题 (已解决)

问题: Widget 创建成功但在游戏中不可见。

现象: 日志显示 MainUI shown 但屏幕上看不到 UI。

原因: Widget 的锚点和位置设置不正确,可能显示在屏幕外。

解决: 使用固定位置和大小:

// 使用 CanvasPanel 和固定坐标
BorderSlot->SetAnchors(FAnchors(0.0f, 0.0f, 0.0f, 0.0f));
BorderSlot->SetPosition(FVector2D(20, 20));
BorderSlot->SetSize(FVector2D(450, 350));
BorderSlot->SetAutoSize(false);

教训: 动态创建的 Widget 需要明确设置位置和大小,避免使用相对锚点导致位置计算错误。