ue-testing-debugging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE Testing & Debugging
Unreal Engine测试与调试
You are an expert in testing, debugging, and profiling Unreal Engine C++ projects.
您是Unreal Engine C++项目测试、调试和性能分析领域的专家。
Context
上下文说明
Read for engine version, existing log categories, test infrastructure (automation modules, test maps), and project-specific conventions before providing guidance.
.agents/ue-project-context.md在提供指导前,请阅读以了解引擎版本、现有日志分类、测试基础设施(自动化模块、测试地图)以及项目特定约定。
.agents/ue-project-context.mdBefore 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., ) that depends on . Include the module in the editor target via and conditionally in the game target via .
MyGameTests"AutomationController"ExtraModuleNamesif (bWithAutomationTests)自动化测试存放在专用模块中(例如),该模块依赖。通过将模块添加到编辑器目标,并通过有条件地添加到游戏目标。
MyGameTests"AutomationController"ExtraModuleNamesif (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_TESTGetTests()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;
}IMPLEMENT_COMPLEX_AUTOMATION_TESTGetTests()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
测试标志参考
| Flag | Meaning |
|---|---|
| Runs in the editor process |
| Runs in game client |
| Runs on dedicated server |
| Fast; runs on every CI check-in |
| Project/game-level tests |
| 标志 | 含义 |
|---|---|
| 在编辑器进程中运行 |
| 在游戏客户端中运行 |
| 在专用服务器上运行 |
| 快速测试;每次CI代码提交时运行 |
| 项目/游戏级测试 |
Latent Commands (Async Testing)
延迟命令(异步测试)
Use latent commands when the test must wait for an async operation. returns when done, to retry next frame.
Update()truefalsecpp
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 for delegate-wait, timeout, and post-async assertion patterns.
references/automation-test-patterns.md当测试需要等待异步操作时,请使用延迟命令。在操作完成时返回,需要重试时返回。
Update()truefalsecpp
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.mdFunctional Tests
功能测试
AFunctionalTestUCLASSStartTest()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"));
}Place actors in . Run via or the Session Frontend.
Maps/Test_MyFeature.umapRunAutomationTest "MyGame.Functional.MyFeature"AFunctionalTestUCLASSStartTest()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放置在中。通过控制台命令或Session Frontend运行测试。
Maps/Test_MyFeature.umapRunAutomationTest "MyGame.Functional.MyFeature"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)
日志级别(从高到低严重程度)
| Level | When to use |
|---|---|
| Crash-worthy unrecoverable errors |
| Operation failed, needs developer attention |
| Unexpected but recoverable condition |
| User-visible output (always shown) |
| Standard development info |
| Detailed per-frame or per-call info |
| Trace-level; very high frequency |
| 级别 | 使用场景 |
|---|---|
| 不可恢复的致命错误,触发崩溃 |
| 操作失败,需要开发者关注 |
| 意外但可恢复的情况 |
| 用户可见的输出(始终显示) |
| 标准开发信息 |
| 详细的每帧或每次调用信息 |
| 跟踪级信息;频率极高 |
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
运行时日志过滤
undefinedundefinedCommand 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 . Understand the build-configuration behaviour before choosing one.
Misc/AssertionMacros.h断言定义在中。选择断言宏前请了解不同构建配置下的行为。
Misc/AssertionMacros.hcheck / 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
undefinedensure / 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
标记不可达的代码路径
| Situation | Macro |
|---|---|
| Class invariant, programmer error | |
| Recoverable condition, want to continue | |
| Expression has side effects always needed | |
| Debug-build heavy validation | |
| User-facing input validation | none — use explicit if/return |
checkNoEntry();
Debug Drawing
标记只能执行一次的代码
Debug draw functions from render geometry directly in the world viewport during PIE or standalone builds. They are stripped by in Shipping.
DrawDebugHelpers.h#if ENABLE_DRAW_DEBUGcpp
#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_DEBUGcheckNoReentry();
undefinedConsole 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 , , , , , , or .
PlayerControllerPawnHUDGameModeGameStateCheatManagerGameInstancecpp
undefinedFAutoConsoleCommand 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 (static init registration), registers at runtime and returns a handle for explicit cleanup.
FAutoConsoleCommandRegisterConsoleCommandensureMsgf(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"));
undefinedDebugging 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
undefinedGameplay 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);
undefinedIDE Breakpoint Debugging
选择指南
Attach Visual Studio or Rider to the running process (Debug > Attach to Process). Use or configuration for full symbol resolution -- strips many symbols.
UnrealEditorDebugGameDebugDevelopmentcpp
// 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 hitFor and failures, set breakpoints on the handler functions above -- they fire before crash/log, letting you inspect the call stack.
check()ensure()| 场景 | 宏 |
|---|---|
| 类不变量、程序员错误 | |
| 可恢复情况,需继续执行 | |
| 表达式包含必须始终执行的副作用 | |
| Debug构建中的重度验证 | |
| 用户输入验证 | 无 — 使用显式if/return逻辑 |
Crash Analysis
调试绘制
- Minidumps land in . Open with
Saved/Crashes/PDB in WinDbg or Rider.UnrealEditor-Win64-DebugGame - prints the current callstack to the log.
FDebug::DumpStackTraceToLog(ELogVerbosity::Error) - submits a callstack to the Crash Reporter without crashing the process.
ensure
Network: for replication capture, for live bandwidth, for actor channels.
-NetTracestat netnet.ListActorChannelsKey profiling commands: / (.uestats capture for Insights), / (GPU timing), / (memory), (Insights launch args). See .
stat startfilestat stopfilestat gpuProfileGPUstat memoryplatformmemreport -full-trace=cpu,gpu,frame,memoryreferences/profiling-commands.md来自的调试绘制函数会在PIE或独立构建中直接在世界视口中渲染几何图形。在Shipping构建中,这些函数会被条件编译移除。
DrawDebugHelpers.h#if ENABLE_DRAW_DEBUGcpp
#include "DrawDebugHelpers.h"Common Mistakes
Duration > 0 = 显示时长(秒);bPersistentLines = true表示永久显示。
—
Duration为0或-1均表示仅显示一帧。
check() in shipping — expressions are compiled out of Shipping builds. Never put required logic inside a check expression; use if the expression must always evaluate.
check()verify()ensure fires only once — After the first ensure failure at a call site, subsequent calls at that site are silent. Use if you need every failure reported.
ensureAlwaysDrawDebug in shipping — DrawDebug calls do not exist in Shipping without . Wrap persistent draws with .
ENABLE_DRAW_DEBUG#if ENABLE_DRAW_DEBUGMissing log category — Defining with a category not visible in the current translation unit causes a linker error. Include the header that declares the category.
UE_LOGAutomation test without a filter flag — Every must have exactly one filter flag (Smoke, Engine, Product, Perf, Stress, or Negative). Missing it is a compile-time failure.
IMPLEMENT_SIMPLE_AUTOMATION_TESTstatic_assertLatent command after — Latent commands are enqueued before the function returns. Do not enqueue them after the statement.
return truereturn true;Log spam in multiplayer — Identical log calls fire from both server and each client. Prefix messages with or use to reduce noise.
GetWorld()->GetNetMode()UE_CLOG(HasAuthority(), ...)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 macro system, delegates, FString, UE_LOG basics
ue-cpp-foundations - — setting up a dedicated test module and target inclusion
ue-module-build-system - — AFunctionalTest placement and world interaction
ue-actor-component-architecture
FlushPersistentDebugLines(World);
References
Shipping构建中保护持久化绘制:#if ENABLE_DRAW_DEBUG ... #endif
—
UKismetSystemLibrary::DrawDebugSphere(及同类函数)会自动受ENABLE_DRAW_DEBUG保护
- — test setup patterns, latent commands, common scenarios
references/automation-test-patterns.md - — stat commands, Insights capture, analysis workflow
references/profiling-commands.md
---—
控制台命令
—
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函数定义在、、、、、或上,在控制台(~)中输入即可执行。
PlayerControllerPawnHUDGameModeGameStateCheatManagerGameInstance—
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附加到运行中的进程(调试 > 附加到进程)。使用或配置以获得完整的符号解析 — 配置会剥离许多符号。
UnrealEditorDebugGameDebugDevelopmentcpp
undefined—
在断言失败时触发断点 — 在以下函数上设置断点:
—
FDebug::AssertFailed (check/checkf)
—
FDebug::EnsureFailed (ensure/ensureMsgf)
—
FDebug::OptionallyLogFormattedEnsureMessageReturningFalse
—
条件断点示例(VS/Rider):
—
条件:Actor->GetName().Contains(TEXT("Enemy"))
—
命中次数:第5次命中时触发断点
—
对于`check()`和`ensure()`失败,在上述处理函数上设置断点 — 它们会在崩溃/日志前触发,让您可以检查调用栈。—
崩溃分析
—
- 小型转储文件存放在中。使用
Saved/Crashes/的PDB文件在WinDbg或Rider中打开。UnrealEditor-Win64-DebugGame - 会将当前调用栈打印到日志中。
FDebug::DumpStackTraceToLog(ELogVerbosity::Error) - 会向崩溃报告器提交调用栈,但不会导致进程崩溃。
ensure
网络调试:使用捕获复制数据,查看实时带宽,查看Actor通道。
-NetTracestat netnet.ListActorChannels关键性能分析命令:/(生成.uestats文件用于Insights分析)、/(GPU计时)、/(内存分析)、(Insights启动参数)。详情请参考。
stat startfilestat stopfilestat gpuProfileGPUstat memoryplatformmemreport -full-trace=cpu,gpu,frame,memoryreferences/profiling-commands.md—
常见错误
—
Shipping构建中使用check() — 表达式在Shipping构建中会被编译移除。永远不要将必需逻辑放在check表达式中;如果表达式必须始终被计算,请使用。
check()verify()ensure仅触发一次 — 某个调用点第一次ensure失败后,该调用点后续的失败会静默处理。如果需要报告每次失败,请使用。
ensureAlwaysShipping构建中使用DrawDebug — 未开启时,DrawDebug调用在Shipping构建中不存在。持久化绘制请用包裹。
ENABLE_DRAW_DEBUG#if ENABLE_DRAW_DEBUG缺失日志分类 — 使用未在当前编译单元中可见的分类定义会导致链接错误。请包含声明该分类的头文件。
UE_LOG自动化测试缺少过滤标志 — 每个必须恰好包含一个过滤标志(Smoke、Engine、Product、Perf、Stress或Negative)。缺失会导致编译时失败。
IMPLEMENT_SIMPLE_AUTOMATION_TESTstatic_assertreturn true后添加延迟命令 — 延迟命令在函数返回前入队。不要在语句后入队延迟命令。
return true;多人游戏中的日志 spam — 相同的日志调用会从服务器和每个客户端触发。使用前缀消息,或使用减少冗余日志。
GetWorld()->GetNetMode()UE_CLOG(HasAuthority(), ...)—
相关技能
—
- — UE宏系统、委托、FString、UE_LOG基础
ue-cpp-foundations - — 设置专用测试模块和目标包含
ue-module-build-system - — AFunctionalTest放置与世界交互
ue-actor-component-architecture
—
参考文档
—
- — 测试设置模式、延迟命令、常见场景
references/automation-test-patterns.md - — stat命令、Insights捕获、分析流程
references/profiling-commands.md