ue-testing-debugging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE Testing & Debugging

Unreal Engine测试与调试

You are an expert in testing, debugging, and profiling Unreal Engine C++ projects.
您是Unreal Engine C++项目测试、调试和性能分析领域的专家。

Context

上下文说明

Read
.agents/ue-project-context.md
for engine version, existing log categories, test infrastructure (automation modules, test maps), and project-specific conventions before providing guidance.
在提供指导前,请阅读
.agents/ue-project-context.md
以了解引擎版本、现有日志分类、测试基础设施(自动化模块、测试地图)以及项目特定约定。

Before You Start

开始前须知

Ask which area the user needs help with if unclear:
  • Automation tests — unit/integration tests using IMPLEMENT_SIMPLE_AUTOMATION_TEST
  • Functional tests — actor-based AFunctionalTest in maps
  • Logging — UE_LOG, custom categories, verbosity filtering
  • Assertions — check, ensure, verify and when to use each
  • Debug drawing — DrawDebug helpers for runtime visualization
  • Console commands — UFUNCTION(Exec), FAutoConsoleCommand, CVars
  • Profiling — Unreal Insights, stat commands, SCOPE_CYCLE_COUNTER

如果不清楚用户需要帮助的领域,请询问具体方向:
  • 自动化测试 — 使用IMPLEMENT_SIMPLE_AUTOMATION_TEST的单元/集成测试
  • 功能测试 — 基于Actor的AFunctionalTest(在地图中运行)
  • 日志记录 — UE_LOG、自定义分类、日志级别过滤
  • 断言 — check、ensure、verify的用法及适用场景
  • 调试绘制 — 用于运行时可视化的DrawDebug工具
  • 控制台命令 — UFUNCTION(Exec)、FAutoConsoleCommand、CVars
  • 性能分析 — Unreal Insights、stat命令、SCOPE_CYCLE_COUNTER

Automation Framework

自动化测试框架

Automation tests live in a dedicated module (e.g.,
MyGameTests
) that depends on
"AutomationController"
. Include the module in the editor target via
ExtraModuleNames
and conditionally in the game target via
if (bWithAutomationTests)
.
自动化测试存放在专用模块中(例如
MyGameTests
),该模块依赖
"AutomationController"
。通过
ExtraModuleNames
将模块添加到编辑器目标,并通过
if (bWithAutomationTests)
有条件地添加到游戏目标。

Simple Tests

简单测试

cpp
// Source/MyGameTests/Private/MyFeature.spec.cpp
#include "Misc/AutomationTest.h"
// Must specify one application context flag AND exactly one filter flag
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMyInventoryTest, "MyGame.Inventory.AddItem",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

bool FMyInventoryTest::RunTest(const FString& Parameters)
{
    UInventoryComponent* Inv = NewObject<UInventoryComponent>();
    Inv->AddItem(FName("Sword"), 1);
    TestEqual(TEXT("Item count after add"), Inv->GetItemCount(FName("Sword")), 1);
    TestTrue(TEXT("Has sword"), Inv->HasItem(FName("Sword")));
    TestFalse(TEXT("No axe"),   Inv->HasItem(FName("Axe")));
    TestNotNull(TEXT("Inv valid"), Inv);
    return true;
}
cpp
// Source/MyGameTests/Private/MyFeature.spec.cpp
#include "Misc/AutomationTest.h"
// 必须指定一个应用上下文标志和恰好一个过滤标志
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMyInventoryTest, "MyGame.Inventory.AddItem",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

bool FMyInventoryTest::RunTest(const FString& Parameters)
{
    UInventoryComponent* Inv = NewObject<UInventoryComponent>();
    Inv->AddItem(FName("Sword"), 1);
    TestEqual(TEXT("Item count after add"), Inv->GetItemCount(FName("Sword")), 1);
    TestTrue(TEXT("Has sword"), Inv->HasItem(FName("Sword")));
    TestFalse(TEXT("No axe"),   Inv->HasItem(FName("Axe")));
    TestNotNull(TEXT("Inv valid"), Inv);
    return true;
}

Complex / Parameterized Tests

复杂/参数化测试

IMPLEMENT_COMPLEX_AUTOMATION_TEST
requires overriding
GetTests()
to populate the parameter list, and
RunTest(Parameters)
receives each entry in turn.
cpp
IMPLEMENT_COMPLEX_AUTOMATION_TEST(FMyAssetLoadTest, "MyGame.Assets.LoadByPath",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

void FMyAssetLoadTest::GetTests(TArray<FString>& OutBeautifiedNames,
                                 TArray<FString>& OutTestCommands) const
{
    OutBeautifiedNames.Add(TEXT("Sword"));  OutTestCommands.Add(TEXT("/Game/Items/BP_Sword"));
    OutBeautifiedNames.Add(TEXT("Shield")); OutTestCommands.Add(TEXT("/Game/Items/BP_Shield"));
}

bool FMyAssetLoadTest::RunTest(const FString& Parameters)
{
    UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *Parameters);
    if (!TestNotNull(TEXT("Asset loaded"), Asset)) { return false; }
    TestTrue(TEXT("Is DataAsset"), Asset->IsA<UPrimaryDataAsset>());
    return true;
}
IMPLEMENT_COMPLEX_AUTOMATION_TEST
需要重写
GetTests()
来填充参数列表,
RunTest(Parameters)
会依次处理每个参数项。
cpp
IMPLEMENT_COMPLEX_AUTOMATION_TEST(FMyAssetLoadTest, "MyGame.Assets.LoadByPath",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

void FMyAssetLoadTest::GetTests(TArray<FString>& OutBeautifiedNames,
                                 TArray<FString>& OutTestCommands) const
{
    OutBeautifiedNames.Add(TEXT("Sword"));  OutTestCommands.Add(TEXT("/Game/Items/BP_Sword"));
    OutBeautifiedNames.Add(TEXT("Shield")); OutTestCommands.Add(TEXT("/Game/Items/BP_Shield"));
}

bool FMyAssetLoadTest::RunTest(const FString& Parameters)
{
    UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *Parameters);
    if (!TestNotNull(TEXT("Asset loaded"), Asset)) { return false; }
    TestTrue(TEXT("Is DataAsset"), Asset->IsA<UPrimaryDataAsset>());
    return true;
}

Test Assertion Methods

测试断言方法

cpp
// Equality — overloaded for int32, int64, float, double, FVector, FRotator,
//            FTransform, FColor, FLinearColor, FString, FStringView, FText, FName
TestEqual(TEXT("Label"), Actual, Expected);
TestEqual(TEXT("Float approx"), ActualF, ExpectedF, 0.001f);   // tolerance overload

// Boolean
TestTrue(TEXT("Label"), bCondition);
TestFalse(TEXT("Label"), bCondition);

// Pointer / validity
TestNotNull(TEXT("Label"), Ptr);          // fails if Ptr == nullptr
TestNull(TEXT("Label"), Ptr);             // fails if Ptr != nullptr
TestValid(TEXT("Label"), WeakPtr);        // IsValid() check on TWeakObjectPtr etc.

// Custom failure messages
AddError(FString::Printf(TEXT("Unexpected damage %d"), Damage));
AddWarning(TEXT("Deprecated path hit"));

// Add error and bail — returns false from RunTest if condition fails
UE_RETURN_ON_ERROR(Ptr != nullptr, TEXT("Ptr must not be null"));
cpp
// 相等断言 — 支持int32、int64、float、double、FVector、FRotator、
//            FTransform、FColor、FLinearColor、FString、FStringView、FText、FName类型
TestEqual(TEXT("Label"), Actual, Expected);
TestEqual(TEXT("Float approx"), ActualF, ExpectedF, 0.001f);   // 带容差的重载

// 布尔断言
TestTrue(TEXT("Label"), bCondition);
TestFalse(TEXT("Label"), bCondition);

// 指针/有效性断言
TestNotNull(TEXT("Label"), Ptr);          // 当Ptr == nullptr时失败
TestNull(TEXT("Label"), Ptr);             // 当Ptr != nullptr时失败
TestValid(TEXT("Label"), WeakPtr);        // 对TWeakObjectPtr等执行IsValid()检查

// 自定义错误信息
AddError(FString::Printf(TEXT("Unexpected damage %d"), Damage));
AddWarning(TEXT("Deprecated path hit"));

// 添加错误并终止 — 若条件失败,从RunTest返回false
UE_RETURN_ON_ERROR(Ptr != nullptr, TEXT("Ptr must not be null"));

Test Flags Reference

测试标志参考

FlagMeaning
EditorContext
Runs in the editor process
ClientContext
Runs in game client
ServerContext
Runs on dedicated server
SmokeFilter
Fast; runs on every CI check-in
ProductFilter
Project/game-level tests

标志含义
EditorContext
在编辑器进程中运行
ClientContext
在游戏客户端中运行
ServerContext
在专用服务器上运行
SmokeFilter
快速测试;每次CI代码提交时运行
ProductFilter
项目/游戏级测试

Latent Commands (Async Testing)

延迟命令(异步测试)

Use latent commands when the test must wait for an async operation.
Update()
returns
true
when done,
false
to retry next frame.
cpp
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitSecondsCommand, float, Duration);
bool FWaitSecondsCommand::Update() { return GetCurrentRunTime() >= Duration; }

// Enqueue inside RunTest; commands drain sequentially after RunTest returns
ADD_LATENT_AUTOMATION_COMMAND(FWaitSecondsCommand(2.0f));
See
references/automation-test-patterns.md
for delegate-wait, timeout, and post-async assertion patterns.

当测试需要等待异步操作时,请使用延迟命令。
Update()
在操作完成时返回
true
,需要重试时返回
false
cpp
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitSecondsCommand, float, Duration);
bool FWaitSecondsCommand::Update() { return GetCurrentRunTime() >= Duration; }

// 在RunTest中入队延迟命令;RunTest返回后命令会按顺序执行
ADD_LATENT_AUTOMATION_COMMAND(FWaitSecondsCommand(2.0f));
关于委托等待、超时和异步后断言模式,请参考
references/automation-test-patterns.md

Functional Tests

功能测试

AFunctionalTest
is a
UCLASS
actor placed in a test map. Override
StartTest()
and call
FinishTest()
when done.
cpp
// MyFunctionalTest.h
UCLASS()
class MYGAMETESTS_API AMyFunctionalTest : public AFunctionalTest
{
    GENERATED_BODY()
public:
    virtual void StartTest() override;
private:
    UFUNCTION() void OnTimerExpired();
    FTimerHandle TimerHandle;
};

// MyFunctionalTest.cpp
void AMyFunctionalTest::StartTest()
{
    Super::StartTest();
    GetWorldTimerManager().SetTimer(TimerHandle, this,
        &AMyFunctionalTest::OnTimerExpired, 1.0f, false);
}

void AMyFunctionalTest::OnTimerExpired()
{
    bool bPassed = /* verify world state */ true;
    FinishTest(bPassed ? EFunctionalTestResult::Succeeded
                       : EFunctionalTestResult::Failed,
               TEXT("Timer-based check"));
}
Place actors in
Maps/Test_MyFeature.umap
. Run via
RunAutomationTest "MyGame.Functional.MyFeature"
or the Session Frontend.
AFunctionalTest
是放置在测试地图中的
UCLASS
Actor。重写
StartTest()
并在完成时调用
FinishTest()
cpp
// MyFunctionalTest.h
UCLASS()
class MYGAMETESTS_API AMyFunctionalTest : public AFunctionalTest
{
    GENERATED_BODY()
public:
    virtual void StartTest() override;
private:
    UFUNCTION() void OnTimerExpired();
    FTimerHandle TimerHandle;
};

// MyFunctionalTest.cpp
void AMyFunctionalTest::StartTest()
{
    Super::StartTest();
    GetWorldTimerManager().SetTimer(TimerHandle, this,
        &AMyFunctionalTest::OnTimerExpired, 1.0f, false);
}

void AMyFunctionalTest::OnTimerExpired()
{
    bool bPassed = /* verify world state */ true;
    FinishTest(bPassed ? EFunctionalTestResult::Succeeded
                       : EFunctionalTestResult::Failed,
               TEXT("Timer-based check"));
}
将Actor放置在
Maps/Test_MyFeature.umap
中。通过控制台命令
RunAutomationTest "MyGame.Functional.MyFeature"
或Session Frontend运行测试。

TimeLimit

时间限制

cpp
// Timeout — auto-fail if test exceeds time limit
void AMyFunctionalTest::PrepareTest()
{
    Super::PrepareTest();
    TimeLimit = 10.0f;  // seconds; 0 = no timeout (default)
}

cpp
// 超时设置 — 若测试超出时间限制则自动失败
void AMyFunctionalTest::PrepareTest()
{
    Super::PrepareTest();
    TimeLimit = 10.0f;  // 秒;0表示无超时(默认值)
}

Logging

日志记录

Declaring and Defining a Category

声明与定义日志分类

cpp
// MyModule.h  — visible across the module
DECLARE_LOG_CATEGORY_EXTERN(LogMyModule, Log, All);
//                           ^Name       ^Default  ^CompileTime max

// MyModule.cpp
DEFINE_LOG_CATEGORY(LogMyModule);

// Single-file static category (no extern needed)
DEFINE_LOG_CATEGORY_STATIC(LogMyHelper, Warning, All);
cpp
// MyModule.h  — 模块内可见
DECLARE_LOG_CATEGORY_EXTERN(LogMyModule, Log, All);
//                           ^名称       ^默认级别  ^编译时最高级别

// MyModule.cpp
DEFINE_LOG_CATEGORY(LogMyModule);

// 单文件静态分类(无需extern声明)
DEFINE_LOG_CATEGORY_STATIC(LogMyHelper, Warning, All);

UE_LOG Usage

UE_LOG用法

cpp
// UE_LOG(CategoryName, Verbosity, Format, ...)
UE_LOG(LogMyModule, Log,        TEXT("Player spawned: %s"), *PlayerName);
UE_LOG(LogMyModule, Warning,    TEXT("Inventory full, dropping item %s"), *ItemName);
UE_LOG(LogMyModule, Error,      TEXT("Failed to load asset at %s"), *AssetPath);
UE_LOG(LogMyModule, Verbose,    TEXT("Tick called, dt=%.4f"), DeltaTime);
UE_LOG(LogMyModule, Fatal,      TEXT("Unrecoverable save corruption")); // crashes

// Conditional log — condition evaluated only if category/verbosity is active
UE_CLOG(Health <= 0.f, LogMyModule, Warning, TEXT("Actor %s has zero health"), *GetName());
cpp
// UE_LOG(分类名称, 日志级别, 格式, ...)
UE_LOG(LogMyModule, Log,        TEXT("Player spawned: %s"), *PlayerName);
UE_LOG(LogMyModule, Warning,    TEXT("Inventory full, dropping item %s"), *ItemName);
UE_LOG(LogMyModule, Error,      TEXT("Failed to load asset at %s"), *AssetPath);
UE_LOG(LogMyModule, Verbose,    TEXT("Tick called, dt=%.4f"), DeltaTime);
UE_LOG(LogMyModule, Fatal,      TEXT("Unrecoverable save corruption")); // 触发崩溃

// 条件日志 — 仅当分类/级别处于激活状态时才会计算条件
UE_CLOG(Health <= 0.f, LogMyModule, Warning, TEXT("Actor %s has zero health"), *GetName());

Verbosity Levels (highest to lowest severity)

日志级别(从高到低严重程度)

LevelWhen to use
Fatal
Crash-worthy unrecoverable errors
Error
Operation failed, needs developer attention
Warning
Unexpected but recoverable condition
Display
User-visible output (always shown)
Log
Standard development info
Verbose
Detailed per-frame or per-call info
VeryVerbose
Trace-level; very high frequency
级别使用场景
Fatal
不可恢复的致命错误,触发崩溃
Error
操作失败,需要开发者关注
Warning
意外但可恢复的情况
Display
用户可见的输出(始终显示)
Log
标准开发信息
Verbose
详细的每帧或每次调用信息
VeryVerbose
跟踪级信息;频率极高

Structured Logging (UE 5.2+)

结构化日志(UE 5.2+)

cpp
#include "Logging/StructuredLog.h"
// Named fields — order does not matter; extra fields beyond the format string are allowed
UE_LOGFMT(LogMyModule, Warning,
    "Loading '{Name}' failed with error {Error}",
    ("Name", AssetName), ("Error", ErrorCode), ("Flags", LoadFlags));
cpp
#include "Logging/StructuredLog.h"
// 命名字段 — 顺序无关;允许格式字符串之外的额外字段
UE_LOGFMT(LogMyModule, Warning,
    "Loading '{Name}' failed with error {Error}",
    ("Name", AssetName), ("Error", ErrorCode), ("Flags", LoadFlags));

Runtime Log Filtering

运行时日志过滤

undefined
undefined

Command line: set a category to Verbose at startup

命令行:启动时设置分类为Verbose级别

-LogCmds="LogMyModule Verbose, LogAI Warning"
-LogCmds="LogMyModule Verbose, LogAI Warning"

Console at runtime

运行时控制台命令

Log LogMyModule Verbose Log LogAI Warning Log reset # restore defaults

---
Log LogMyModule Verbose Log LogAI Warning Log reset # 恢复默认设置

---

Assertions

断言

Assertions are defined in
Misc/AssertionMacros.h
. Understand the build-configuration behaviour before choosing one.
断言定义在
Misc/AssertionMacros.h
中。选择断言宏前请了解不同构建配置下的行为。

check / checkf

check / checkf

cpp
// Active in Debug, Development, Test. REMOVED in Shipping (expression not evaluated).
check(Ptr != nullptr);
checkf(Index >= 0 && Index < Array.Num(),
       TEXT("Index %d out of range [0,%d)"), Index, Array.Num());

// Debug-only (DO_GUARD_SLOW — only Debug builds)
checkSlow(ExpensiveValidationFn());

// Marks unreachable code paths
checkNoEntry();

// Marks code that must only execute once
checkNoReentry();
cpp
undefined

ensure / ensureMsgf

在Debug、Development、Test构建中激活。Shipping构建中会被移除(表达式不会被计算)。

cpp
// Non-fatal. Logs callstack and submits crash report. Execution continues.
// Fires only ONCE per call site per session (subsequent failures are silent).
if (ensure(Component != nullptr))
{
    Component->DoWork();  // safe to call here
}

// With a message
ensureMsgf(Health > 0.f, TEXT("Actor %s has non-positive health %.1f"),
           *GetName(), Health);

// Always fires (not just once per session)
ensureAlways(bInitialized);
ensureAlwaysMsgf(bInitialized, TEXT("System not initialized before use"));
check(Ptr != nullptr); checkf(Index >= 0 && Index < Array.Num(), TEXT("Index %d out of range [0,%d)"), Index, Array.Num());

verify / verifyf

仅Debug构建有效(DO_GUARD_SLOW — 仅Debug构建)

cpp
// Expression ALWAYS evaluated (even in Shipping), but only halts in non-Shipping.
// Use when expression has side effects you always need.
verify(Manager->Init());
verifyf(Count++ < MaxCount, TEXT("Exceeded max count %d"), MaxCount);
checkSlow(ExpensiveValidationFn());

Decision Guide

标记不可达的代码路径

SituationMacro
Class invariant, programmer error
check
/
checkf
Recoverable condition, want to continue
ensure
/
ensureMsgf
Expression has side effects always needed
verify
/
verifyf
Debug-build heavy validation
checkSlow
User-facing input validationnone — use explicit if/return

checkNoEntry();

Debug Drawing

标记只能执行一次的代码

Debug draw functions from
DrawDebugHelpers.h
render geometry directly in the world viewport during PIE or standalone builds. They are stripped by
#if ENABLE_DRAW_DEBUG
in Shipping.
cpp
#include "DrawDebugHelpers.h"

// Duration > 0 = seconds visible; bPersistentLines = true for permanent.
// Duration 0 or -1 both show for one frame.
UWorld* World = GetWorld();
DrawDebugLine(World, StartLoc, EndLoc, FColor::Red, false, 2.0f, 0, 2.0f);
DrawDebugSphere(World, Center, Radius, 12, FColor::Green, false, 2.0f);
DrawDebugBox(World, Center, Extent, FColor::Blue, false, 2.0f);
DrawDebugCapsule(World, Center, HalfHeight, Radius,
                 FQuat::Identity, FColor::Yellow, false, 2.0f);
DrawDebugPoint(World, Location, 8.0f, FColor::White, false, 2.0f);
DrawDebugDirectionalArrow(World, Start, End, 40.0f, FColor::Cyan, false, 2.0f);
DrawDebugString(World, Location, TEXT("Label"), nullptr,
                FColor::White, 2.0f, true);

// Clear all persistent debug geometry
FlushPersistentDebugLines(World);

// Guard persistent draws for shipping: #if ENABLE_DRAW_DEBUG ... #endif
// UKismetSystemLibrary::DrawDebugSphere (and siblings) auto-gate behind ENABLE_DRAW_DEBUG

checkNoReentry();
undefined

Console Commands

ensure / ensureMsgf

Exec Functions

cpp
// AMyPlayerController.h
UFUNCTION(Exec)
void ToggleGodMode();

// AMyPlayerController.cpp
void AMyPlayerController::ToggleGodMode()
{
    bGodMode = !bGodMode;
    UE_LOG(LogMyModule, Display, TEXT("GodMode: %s"), bGodMode ? TEXT("ON") : TEXT("OFF"));
}
Exec functions work when typed in the console (~) if on a
PlayerController
,
Pawn
,
HUD
,
GameMode
,
GameState
,
CheatManager
, or
GameInstance
.
cpp
undefined

FAutoConsoleCommand and CVars

非致命断言。记录调用栈并提交崩溃报告。程序继续执行。

每个调用点每会话仅触发一次(后续失败会静默处理)。

cpp
// Registers at static init; no world context needed
static FAutoConsoleCommand GDumpStatsCmd(
    TEXT("MyGame.DumpStats"), TEXT("Dump gameplay stats"),
    FConsoleCommandDelegate::CreateLambda([]()
    {
        UE_LOG(LogMyModule, Display, TEXT("=== GameStats ==="));
    }));

// With world + args (PIE-safe; prefer this for gameplay commands)
static FAutoConsoleCommandWithWorldAndArgs GSpawnItemCmd(
    TEXT("MyGame.SpawnItem"), TEXT("Usage: MyGame.SpawnItem <Name>"),
    FConsoleCommandWithWorldAndArgsDelegate::CreateLambda(
        [](const TArray<FString>& Args, UWorld* World) { /* spawn */ }));

// CVar — declare in .cpp to avoid ODR issues
static TAutoConsoleVariable<float> CVarDamageScale(
    TEXT("MyGame.DamageScale"), 1.0f,
    TEXT("Scales all outgoing damage."), ECVF_Cheat);
float Scale = CVarDamageScale.GetValueOnGameThread();

// Attach CVar to an existing variable
float GDrawDistance = 5000.0f;
static FAutoConsoleVariableRef CVarDrawDistance(
    TEXT("MyGame.DrawDistance"), GDrawDistance,
    TEXT("Draw distance for gameplay objects"), ECVF_Default);
if (ensure(Component != nullptr)) { Component->DoWork(); // 此处调用安全 }

Runtime Command Registration

带自定义信息

cpp
// Runtime registration — call from StartupModule, Initialize, or BeginPlay
IConsoleCommand* Cmd = IConsoleManager::Get().RegisterConsoleCommand(
    TEXT("MyGame.ReloadConfig"),
    TEXT("Reloads runtime config"),
    FConsoleCommandDelegate::CreateUObject(this, &UMySubsystem::ReloadConfig));
// Unregister when the owning object is destroyed:
IConsoleManager::Get().UnregisterConsoleObject(Cmd);
Unlike
FAutoConsoleCommand
(static init registration),
RegisterConsoleCommand
registers at runtime and returns a handle for explicit cleanup.

ensureMsgf(Health > 0.f, TEXT("Actor %s has non-positive health %.1f"), *GetName(), Health);

Custom Stat Groups & Profiling Markers

每次失败都会触发(并非每会话仅一次)

cpp
// Declare in a .cpp — feeds stat commands and Unreal Insights
DECLARE_STATS_GROUP(TEXT("MyGame"), STATGROUP_MyGame, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("MySystem Tick"), STAT_MySystemTick, STATGROUP_MyGame);
DECLARE_CYCLE_STAT(TEXT("PathFind"),      STAT_PathFind,     STATGROUP_MyGame);

void UMySystem::Tick(float DeltaTime) { SCOPE_CYCLE_COUNTER(STAT_MySystemTick); }
FPath UMySystem::FindPath(...)        { SCOPE_CYCLE_COUNTER(STAT_PathFind); }

// Named events — colored blocks in Insights, no stat overhead
#include "HAL/PlatformMisc.h"
void UMySystem::HeavyOperation() { SCOPED_NAMED_EVENT(MyHeavyOp, FColor::Orange); }

// CSV profiling — lightweight always-on telemetry written to Saved/Profiling/CSVStats/
#include "ProfilingDebugging/CsvProfiler.h"
CSV_DEFINE_CATEGORY(MyGame, true);
void UMySystem::Tick(float DeltaTime) { CSV_SCOPED_TIMING_STAT(MyGame, SystemTick); }

ensureAlways(bInitialized); ensureAlwaysMsgf(bInitialized, TEXT("System not initialized before use"));
undefined

Debugging Techniques

verify / verifyf

Visual Logger

cpp
#include "VisualLogger/VisualLogger.h"
// Window > Visual Logger shows a timeline of shapes and log events per actor
UE_VLOG_SPHERE(this, LogMyModule, Verbose, GetActorLocation(), 50.0f, FColor::Green, TEXT("Patrol point"));
UE_VLOG_SEGMENT(this, LogMyModule, Log, From, To, FColor::Red, TEXT("Path"));
UE_VLOG(this, LogMyModule, Log, TEXT("State: %s"), *StateName);
cpp
undefined

Gameplay Debugger

表达式始终会被计算(即使在Shipping构建中),但仅在非Shipping构建中终止程序。

当表达式包含必须始终执行的副作用时使用。

Press
'
(apostrophe) in PIE to open the Gameplay Debugger. It shows AI, EQS, Ability System, and custom categories. Register a custom category in module startup:
cpp
IGameplayDebugger::Get().RegisterCategory("MySystem",
    IGameplayDebugger::FOnGetCategory::CreateStatic(&FMyDebugCategory::MakeInstance),
    EGameplayDebuggerCategoryState::EnabledInGame);
verify(Manager->Init()); verifyf(Count++ < MaxCount, TEXT("Exceeded max count %d"), MaxCount);
undefined

IDE Breakpoint Debugging

选择指南

Attach Visual Studio or Rider to the running
UnrealEditor
process (Debug > Attach to Process). Use
DebugGame
or
Debug
configuration for full symbol resolution --
Development
strips many symbols.
cpp
// Break on assertion failures -- set breakpoints on these functions:
// FDebug::AssertFailed       (check/checkf)
// FDebug::EnsureFailed       (ensure/ensureMsgf)
// FDebug::OptionallyLogFormattedEnsureMessageReturningFalse

// Conditional breakpoint example (VS/Rider):
// Condition: Actor->GetName().Contains(TEXT("Enemy"))
// Hit count: break on 5th hit
For
check()
and
ensure()
failures, set breakpoints on the handler functions above -- they fire before crash/log, letting you inspect the call stack.
场景
类不变量、程序员错误
check
/
checkf
可恢复情况,需继续执行
ensure
/
ensureMsgf
表达式包含必须始终执行的副作用
verify
/
verifyf
Debug构建中的重度验证
checkSlow
用户输入验证 — 使用显式if/return逻辑

Crash Analysis

调试绘制

  • Minidumps land in
    Saved/Crashes/
    . Open with
    UnrealEditor-Win64-DebugGame
    PDB in WinDbg or Rider.
  • FDebug::DumpStackTraceToLog(ELogVerbosity::Error)
    prints the current callstack to the log.
  • ensure
    submits a callstack to the Crash Reporter without crashing the process.
Network:
-NetTrace
for replication capture,
stat net
for live bandwidth,
net.ListActorChannels
for actor channels.
Key profiling commands:
stat startfile
/
stat stopfile
(.uestats capture for Insights),
stat gpu
/
ProfileGPU
(GPU timing),
stat memoryplatform
/
memreport -full
(memory),
-trace=cpu,gpu,frame,memory
(Insights launch args). See
references/profiling-commands.md
.

来自
DrawDebugHelpers.h
的调试绘制函数会在PIE或独立构建中直接在世界视口中渲染几何图形。在Shipping构建中,这些函数会被
#if ENABLE_DRAW_DEBUG
条件编译移除。
cpp
#include "DrawDebugHelpers.h"

Common Mistakes

Duration > 0 = 显示时长(秒);bPersistentLines = true表示永久显示。

Duration为0或-1均表示仅显示一帧。

check() in shipping
check()
expressions are compiled out of Shipping builds. Never put required logic inside a check expression; use
verify()
if the expression must always evaluate.
ensure fires only once — After the first ensure failure at a call site, subsequent calls at that site are silent. Use
ensureAlways
if you need every failure reported.
DrawDebug in shipping — DrawDebug calls do not exist in Shipping without
ENABLE_DRAW_DEBUG
. Wrap persistent draws with
#if ENABLE_DRAW_DEBUG
.
Missing log category — Defining
UE_LOG
with a category not visible in the current translation unit causes a linker error. Include the header that declares the category.
Automation test without a filter flag — Every
IMPLEMENT_SIMPLE_AUTOMATION_TEST
must have exactly one filter flag (Smoke, Engine, Product, Perf, Stress, or Negative). Missing it is a compile-time
static_assert
failure.
Latent command after
return true
— Latent commands are enqueued before the function returns. Do not enqueue them after the
return true;
statement.
Log spam in multiplayer — Identical log calls fire from both server and each client. Prefix messages with
GetWorld()->GetNetMode()
or use
UE_CLOG(HasAuthority(), ...)
to reduce noise.

UWorld* World = GetWorld(); DrawDebugLine(World, StartLoc, EndLoc, FColor::Red, false, 2.0f, 0, 2.0f); DrawDebugSphere(World, Center, Radius, 12, FColor::Green, false, 2.0f); DrawDebugBox(World, Center, Extent, FColor::Blue, false, 2.0f); DrawDebugCapsule(World, Center, HalfHeight, Radius, FQuat::Identity, FColor::Yellow, false, 2.0f); DrawDebugPoint(World, Location, 8.0f, FColor::White, false, 2.0f); DrawDebugDirectionalArrow(World, Start, End, 40.0f, FColor::Cyan, false, 2.0f); DrawDebugString(World, Location, TEXT("Label"), nullptr, FColor::White, 2.0f, true);

Related Skills

清除所有持久化调试几何图形

  • ue-cpp-foundations
    — UE macro system, delegates, FString, UE_LOG basics
  • ue-module-build-system
    — setting up a dedicated test module and target inclusion
  • ue-actor-component-architecture
    — AFunctionalTest placement and world interaction

FlushPersistentDebugLines(World);

References

Shipping构建中保护持久化绘制:#if ENABLE_DRAW_DEBUG ... #endif

UKismetSystemLibrary::DrawDebugSphere(及同类函数)会自动受ENABLE_DRAW_DEBUG保护

  • references/automation-test-patterns.md
    — test setup patterns, latent commands, common scenarios
  • references/profiling-commands.md
    — stat commands, Insights capture, analysis workflow

---

控制台命令

Exec函数

cpp
// AMyPlayerController.h
UFUNCTION(Exec)
void ToggleGodMode();

// AMyPlayerController.cpp
void AMyPlayerController::ToggleGodMode()
{
    bGodMode = !bGodMode;
    UE_LOG(LogMyModule, Display, TEXT("GodMode: %s"), bGodMode ? TEXT("ON") : TEXT("OFF"));
}
如果Exec函数定义在
PlayerController
Pawn
HUD
GameMode
GameState
CheatManager
GameInstance
上,在控制台(~)中输入即可执行。

FAutoConsoleCommand与CVars

cpp
// 静态初始化时注册;无需世界上下文
static FAutoConsoleCommand GDumpStatsCmd(
    TEXT("MyGame.DumpStats"), TEXT("Dump gameplay stats"),
    FConsoleCommandDelegate::CreateLambda([]()
    {
        UE_LOG(LogMyModule, Display, TEXT("=== GameStats ==="));
    }));

带世界上下文和参数(PIE安全;游戏玩法命令优先使用此方式)

static FAutoConsoleCommandWithWorldAndArgs GSpawnItemCmd( TEXT("MyGame.SpawnItem"), TEXT("Usage: MyGame.SpawnItem <Name>"), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda( [](const TArray<FString>& Args, UWorld* World) { /* spawn */ }));

CVar — 在.cpp中声明以避免ODR问题

static TAutoConsoleVariable<float> CVarDamageScale( TEXT("MyGame.DamageScale"), 1.0f, TEXT("Scales all outgoing damage."), ECVF_Cheat); float Scale = CVarDamageScale.GetValueOnGameThread();

将CVar绑定到现有变量

float GDrawDistance = 5000.0f; static FAutoConsoleVariableRef CVarDrawDistance( TEXT("MyGame.DrawDistance"), GDrawDistance, TEXT("Draw distance for gameplay objects"), ECVF_Default);
undefined

运行时命令注册

cpp
undefined

运行时注册 — 从StartupModule、Initialize或BeginPlay中调用

IConsoleCommand* Cmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("MyGame.ReloadConfig"), TEXT("Reloads runtime config"), FConsoleCommandDelegate::CreateUObject(this, &UMySubsystem::ReloadConfig));

当所属对象销毁时注销:

IConsoleManager::Get().UnregisterConsoleObject(Cmd);

与`FAutoConsoleCommand`(静态初始化时注册)不同,`RegisterConsoleCommand`在运行时注册并返回句柄用于显式清理。

---

自定义统计组与性能分析标记

cpp
undefined

在.cpp中声明 — 为stat命令和Unreal Insights提供数据

DECLARE_STATS_GROUP(TEXT("MyGame"), STATGROUP_MyGame, STATCAT_Advanced); DECLARE_CYCLE_STAT(TEXT("MySystem Tick"), STAT_MySystemTick, STATGROUP_MyGame); DECLARE_CYCLE_STAT(TEXT("PathFind"), STAT_PathFind, STATGROUP_MyGame);
void UMySystem::Tick(float DeltaTime) { SCOPE_CYCLE_COUNTER(STAT_MySystemTick); } FPath UMySystem::FindPath(...) { SCOPE_CYCLE_COUNTER(STAT_PathFind); }

命名事件 — 在Insights中显示为彩色块,无统计开销

#include "HAL/PlatformMisc.h" void UMySystem::HeavyOperation() { SCOPED_NAMED_EVENT(MyHeavyOp, FColor::Orange); }

CSV性能分析 — 轻量级始终开启的遥测数据,写入Saved/Profiling/CSVStats/

#include "ProfilingDebugging/CsvProfiler.h" CSV_DEFINE_CATEGORY(MyGame, true); void UMySystem::Tick(float DeltaTime) { CSV_SCOPED_TIMING_STAT(MyGame, SystemTick); }

---

调试技巧

可视化日志

cpp
#include "VisualLogger/VisualLogger.h"

窗口 > 可视化日志 显示每个Actor的形状和日志事件时间线

UE_VLOG_SPHERE(this, LogMyModule, Verbose, GetActorLocation(), 50.0f, FColor::Green, TEXT("Patrol point")); UE_VLOG_SEGMENT(this, LogMyModule, Log, From, To, FColor::Red, TEXT("Path")); UE_VLOG(this, LogMyModule, Log, TEXT("State: %s"), *StateName);
undefined

游戏玩法调试器

在PIE中按
'
(撇号)打开游戏玩法调试器。它会显示AI、EQS、技能系统和自定义分类。在模块启动时注册自定义分类:
cpp
IGameplayDebugger::Get().RegisterCategory("MySystem",
    IGameplayDebugger::FOnGetCategory::CreateStatic(&FMyDebugCategory::MakeInstance),
    EGameplayDebuggerCategoryState::EnabledInGame);

IDE断点调试

将Visual Studio或Rider附加到运行中的
UnrealEditor
进程(调试 > 附加到进程)。使用
DebugGame
Debug
配置以获得完整的符号解析 —
Development
配置会剥离许多符号。
cpp
undefined

在断言失败时触发断点 — 在以下函数上设置断点:

FDebug::AssertFailed (check/checkf)

FDebug::EnsureFailed (ensure/ensureMsgf)

FDebug::OptionallyLogFormattedEnsureMessageReturningFalse

条件断点示例(VS/Rider):

条件:Actor->GetName().Contains(TEXT("Enemy"))

命中次数:第5次命中时触发断点


对于`check()`和`ensure()`失败,在上述处理函数上设置断点 — 它们会在崩溃/日志前触发,让您可以检查调用栈。

崩溃分析

  • 小型转储文件存放在
    Saved/Crashes/
    中。使用
    UnrealEditor-Win64-DebugGame
    的PDB文件在WinDbg或Rider中打开。
  • FDebug::DumpStackTraceToLog(ELogVerbosity::Error)
    会将当前调用栈打印到日志中。
  • ensure
    会向崩溃报告器提交调用栈,但不会导致进程崩溃。
网络调试:使用
-NetTrace
捕获复制数据,
stat net
查看实时带宽,
net.ListActorChannels
查看Actor通道。
关键性能分析命令
stat startfile
/
stat stopfile
(生成.uestats文件用于Insights分析)、
stat gpu
/
ProfileGPU
(GPU计时)、
stat memoryplatform
/
memreport -full
(内存分析)、
-trace=cpu,gpu,frame,memory
(Insights启动参数)。详情请参考
references/profiling-commands.md

常见错误

Shipping构建中使用check()
check()
表达式在Shipping构建中会被编译移除。永远不要将必需逻辑放在check表达式中;如果表达式必须始终被计算,请使用
verify()
ensure仅触发一次 — 某个调用点第一次ensure失败后,该调用点后续的失败会静默处理。如果需要报告每次失败,请使用
ensureAlways
Shipping构建中使用DrawDebug — 未开启
ENABLE_DRAW_DEBUG
时,DrawDebug调用在Shipping构建中不存在。持久化绘制请用
#if ENABLE_DRAW_DEBUG
包裹。
缺失日志分类 — 使用未在当前编译单元中可见的分类定义
UE_LOG
会导致链接错误。请包含声明该分类的头文件。
自动化测试缺少过滤标志 — 每个
IMPLEMENT_SIMPLE_AUTOMATION_TEST
必须恰好包含一个过滤标志(Smoke、Engine、Product、Perf、Stress或Negative)。缺失会导致编译时
static_assert
失败。
return true后添加延迟命令 — 延迟命令在函数返回前入队。不要在
return true;
语句后入队延迟命令。
多人游戏中的日志 spam — 相同的日志调用会从服务器和每个客户端触发。使用
GetWorld()->GetNetMode()
前缀消息,或使用
UE_CLOG(HasAuthority(), ...)
减少冗余日志。

相关技能

  • ue-cpp-foundations
    — UE宏系统、委托、FString、UE_LOG基础
  • ue-module-build-system
    — 设置专用测试模块和目标包含
  • ue-actor-component-architecture
    — AFunctionalTest放置与世界交互

参考文档

  • references/automation-test-patterns.md
    — 测试设置模式、延迟命令、常见场景
  • references/profiling-commands.md
    — stat命令、Insights捕获、分析流程