mcpField
U
ue-testing-debugging

ue-testing-debugging

github.com/quodsoler测试与质量4191.3k 次安装4 周前更新
代码仓库

关于 ue-testing-debugging

面向 Unreal Engine 5.8 开发者的技能,覆盖日志、断言、自动化与功能测试、控制台命令和性能分析。它也介绍调试可视化及不同构建配置下的相关行为。

主要功能

  1. 01配置日志类别、级别与结构化日志
  2. 02选择断言方式并了解构建差异
  3. 03编写自动化、CQTest 和功能测试
  4. 04使用控制台命令与控制台变量
  5. 05分析性能并使用调试可视化

适用场景

  1. 01为 UE 模块记录和筛查运行信息
  2. 02为游戏逻辑编写自动化测试
  3. 03排查性能问题或可视化运行状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-testing-debugging

安装后在 Claude Code 中输入/ue-testing-debugging确认已加载。

附带文件references/automation-test-patterns.mdreferences/profiling-commands.md

详细介绍

UE 测试、日志记录与性能分析

目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;旧版形式列于“已弃用 — 请勿使用”下。

本技能负责本仓库的日志记录、断言、自动化/功能测试、控制台命令和性能分析。日志记录、断言、统计、Trace 和 CVar 位于 Core(Logging/、Misc/AssertionMacros.h、Stats/Stats.h、ProfilingDebugging/、HAL/IConsoleManager.h)。调试绘制、Visual Logger 和 AHUD::ShowDebug 位于 Engine。测试框架属于开发者模块 AutomationController、CQTest、FunctionalTesting 和 AutomationDriver;Gameplay Debugger 是运行时模块 GameplayDebugger。

上下文

如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用插件、GAS/网络设置)。如果该文件缺失,不要因此停止。根据请求和代码库确定相关领域;只有在两种合理解读会产生不同代码时才提问。

请求涉及……前往
UE_LOG、类别、详细级别、结构化日志记录日志记录
check、ensure、verify、错误状态时崩溃断言
单元测试、IMPLEMENT_SIMPLE_AUTOMATION_TEST、规格测试、潜伏命令自动化测试
TEST_CLASS、ASSERT_THAT、夹具式测试CQTest
放置在地图中的测试、AFunctionalTest功能测试
Insights、stat、周期计数器、CSV、内存、卡顿性能分析
DrawDebug*、UE_VLOG、Gameplay Debugger、ShowDebug调试可视化
Exec 函数、FAutoConsoleCommand、CVar控制台命令和 CVar

日志记录

声明类别
#include "Logging/LogMacros.h"
// MyGameLogging.h — DECLARE_LOG_CATEGORY_EXTERN(Name, DefaultVerbosity, CompileTimeVerbosity)
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All);

DEFINE_LOG_CATEGORY(LogMyGame);   // MyGameLogging.cpp
宏位置作用范围
DECLARE_LOG_CATEGORY_EXTERN(Name, Default, CompileTime)头文件所属模块及所有包含该头文件的代码
DEFINE_LOG_CATEGORY(Name)一个 .cpp 文件与 EXTERN 形式配对
DEFINE_LOG_CATEGORY_STATIC(Name, Default, CompileTime)一个 .cpp 文件仅限该翻译单元
DECLARE_LOG_CATEGORY_CLASS(Name, Default, CompileTime)类体类静态类别;与 DEFINE_LOG_CATEGORY_CLASS(Class, Name) 配对

DefaultVerbosity 必须 <= CompileTimeVerbosity,否则类别宏内部的 static_assert 会触发。高于 CompileTimeVerbosity 的内容会被编译器删除,因此 DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, Log) 会移除所有 Verbose/VeryVerbose 调用点。单体构建可通过定义 COMPILED_IN_MINIMUM_VERBOSITY 一次性限制所有类别。

详细级别
级别控制台文件说明
Fatal是是导致进程崩溃,即使禁用了日志记录也一样
Error是是会被命令程序和编辑器收集;导致命令程序失败
Warning是是可以提升为错误
Display是是用户必须看到该行时使用
Log否是仅写入文件 — 开发消息的默认级别
Verbose否是仅当类别提升至 Verbose 后才会记录
VeryVerbose否是仅当类别提升至 VeryVerbose 后才会记录

ELogVerbosity::All 是 VeryVerbose 的别名,通常用作 CompileTimeVerbosity 参数。

输出日志
UE_LOG(LogMyGame, Display, TEXT("Player %s spawned"), *PlayerName);
UE_LOG(LogMyGame, Warning, TEXT("Inventory full, dropped %s"), *ItemName);
UE_LOG(LogMyGame, Error,   TEXT("Failed to load asset %s"), *AssetPath);
UE_LOG(LogMyGame, Fatal,   TEXT("Save data unrecoverable"));   // crashes

// Condition and arguments are evaluated only when the category is active.
UE_CLOG(Health <= 0.f, LogMyGame, Warning, TEXT("%s has zero health"), *GetName());

将开销较大的报告逻辑包在 if (UE_LOG_ACTIVE(LogMyGame, Verbose)) { … } 中,这样跳过的是实际工作,而不仅仅是格式化。

结构化日志记录

UE_LOGFMT 会输出可保留在 Insights 和日志分析中的命名字段;使用 {Name} 占位符,而不是 %s。

#include "Logging/StructuredLog.h"

// Named fields: order is irrelevant, extra fields are allowed.
UE_LOGFMT(LogMyGame, Warning, "Loading '{Name}' failed with error {Error}",
    ("Name", AssetName), ("Error", ErrorCode), ("Flags", LoadFlags));

// Positional fields: the values must match the placeholders exactly.
UE_LOGFMT(LogMyGame, Display, "Spawned {Count} actors in {Seconds}s", SpawnCount, Elapsed);
UE_CLOGFMT(bFailed, LogMyGame, Error, "Retry {Attempt} failed", ("Attempt", Attempt));

字段名称必须匹配 [A-Za-z0-9_]+,且每个事件中的名称必须唯一;值通过 SerializeForLog 或 operator<<(FCbWriter&, T) 序列化。UE_LOGFMT 最多接受 16 个字段;超出时使用 UE_LOGFMT_EX 搭配 UE_LOGFMT_FIELD / UE_LOGFMT_VALUE。

Shipping 和 Test 构建

在 Test 和 Shipping 中,NO_LOGGING 均为 !USE_LOGGING_IN_SHIPPING,在 Debug 和 Development 中则为 0。启用 NO_LOGGING 时,UE_LOG/UE_CLOG/UE_LOGFMT 会收敛为仅处理 Fatal 的路径:所有非致命调用都会消失,包括其参数。在 MyGame.Target.cs 中设置 bUseLoggingInShipping = true;,即可定义 USE_LOGGING_IN_SHIPPING=1 并恢复日志记录。

在运行时控制详细级别
-LogCmds="LogMyGame Verbose, LogNet Warning"   # command line, applied at boot
Log list [Filter]       # every category with its current verbosity
Log LogMyGame Verbose   # raise one category
Log global none         # silence everything
Log reset               # restore defaults

在代码中使用 UE_SET_LOG_VERBOSITY(LogMyGame, Verbose);在配置中使用 DefaultEngine.ini 中的 [Core.Log]。

断言

所有定义都在 Misc/AssertionMacros.h 中。根据出错的一方选择:程序员错误使用 check,值得报告且可恢复的意外情况使用 ensure,用户输入或数据输入使用普通的 if。

check(Component != nullptr);
checkf(Index >= 0 && Index < Items.Num(), TEXT("Index %d out of [0,%d)"), Index, Items.Num());
checkNoEntry();                             // unreachable branch
checkNoReentry();                           // must not be entered twice
checkSlow(ValidateExpensiveInvariant());    // Debug only
verify(Manager->Initialize());              // expression ALWAYS evaluated

if (ensure(Component != nullptr))
{
    Component->Activate();                  // ensure() returns the condition
}
ensureMsgf(Health > 0.f, TEXT("%s has health %.1f"), *GetName(), Health);
ensureAlways(bInitialized);                 // reports every failure, not just the first

ensure/ensureMsgf 在每个会话中每个调用位置仅报告一次;ensureAlways/ensureAlwaysMsgf 则每次都会报告。四者都会返回条件值,因此可以与 if 组合使用。

构建DO_CHECKDO_ENSUREDO_GUARD_SLOWNO_LOGGING
Debug1110
Development1100
Test 和 ShippingUSE_CHECKS_IN_SHIPPINGUSE_ENSURES_IN_SHIPPING0!USE_LOGGING_IN_SHIPPING

USE_CHECKS_IN_SHIPPING 默认为 0,由目标规则中的 bUseChecksInShipping 设置,因此默认情况下,Test 构建也会移除 check 和 ensure。当 DO_CHECK 为 0 时,check(expr) 会退化为 CA_ASSUME(expr),且不会对表达式求值;verify(expr) 仍会对表达式求值,只是不再中止程序。

自动化测试

测试源文件位于被测模块的 Private/Tests 文件夹中,或位于专用测试模块中。每个测试文件都要用 #if WITH_DEV_AUTOMATION_TESTS(或 WITH_AUTOMATION_TESTS = WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS)包裹——仅使用 IMPLEMENT_* 宏仍会编译类;只有在 WITH_AUTOMATION_WORKER 为 0 时才会跳过注册(Misc/AutomationTest.h:4366);UBT 会在除 Test 和 Shipping 以外的所有配置中启用这两个宏,可通过 bForceCompileDevelopmentAutomationTests、bForceCompilePerformanceAutomationTests 和 bForceDisableAutomationTests 覆盖(UEBuildTarget.cs:6309-6325)。模块和目标的配置方式、完整的模式库以及运行测试的命令行请参阅 references/automation-test-patterns.md。

// Source/MyGame/Private/Tests/MyInventoryTest.cpp — whole file inside #if WITH_DEV_AUTOMATION_TESTS
#include "Misc/AutomationTest.h"
#include "MyInventoryComponent.h"

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMyInventoryAddTest, "MyGame.Inventory.AddItem",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

bool FMyInventoryAddTest::RunTest(const FString& Parameters)
{
    UMyInventoryComponent* Inv = NewObject<UMyInventoryComponent>(GetTransientPackage());
    UE_RETURN_ON_ERROR(Inv != nullptr, TEXT("Inventory created"));

    Inv->AddItem(FName("Sword"), 1);
    TestEqual(TEXT("Count after add"), Inv->GetItemCount(FName("Sword")), 1);
    TestTrue(TEXT("Has sword"), Inv->HasItem(FName("Sword")));
    TestFalse(TEXT("No axe"), Inv->HasItem(FName("Axe")));
    return true;
}

IMPLEMENT_COMPLEX_AUTOMATION_TEST 会添加 virtual void GetTests(TArray<FString>& OutBeautifiedNames, TArray<FString>& OutTestCommands) const,并针对每个条目运行一次 RunTest。IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(TClass, TBaseClass, PrettyName, TFlags) 会声明一个简单测试,其基类是你从 FAutomationTestBase 派生出的共享夹具。

标志

EAutomationTestFlags 是带有 ENUM_CLASS_FLAGS 的 enum class,因此可使用 | 组合。两个 static_assert 会强制要求至少一个上下文标志和恰好一个筛选标志。

组值
上下文(至少需要 1 个)EditorContext、ClientContext、ServerContext、CommandletContext、ProgramContext
筛选(恰好 1 个)SmokeFilter、EngineFilter、ProductFilter、PerfFilter、StressFilter、NegativeFilter
优先级CriticalPriority、HighPriority、MediumPriority、LowPriority
其他NonNullRHI、RequiresUser、Disabled、SupportsAutoRTFM

EAutomationTestFlags_ApplicationContextMask 可用一个标记涵盖所有上下文标志。

测试中的断言
TestEqual(TEXT("Label"), Actual, Expected);              // int32/int64/SIZE_T/FName/FText/FString/FStringView…
TestEqual(TEXT("Approx"), ActualF, ExpectedF, 0.001f);   // float/double/FVector/FRotator/FTransform take a tolerance
TestTrue(TEXT("Label"), bCondition);                     // also TestFalse, TestNotEqual
TestNotNull(TEXT("Label"), Ptr);                         // also TestNull, TestSamePtr, TestNotSamePtr
TestValid(TEXT("Label"), SharedOrWeakPtr);               // also TestInvalid

AddError(FString::Printf(TEXT("Unexpected damage %d"), Damage));   // also AddWarning, AddInfo
AddErrorIfFalse(bOk, TEXT("Setup failed"));
UE_RETURN_ON_ERROR(Ptr != nullptr, TEXT("Ptr must not be null")); // adds error, returns false

// Expect (and swallow) a log line the code under test is supposed to emit.
AddExpectedMessage(TEXT("Inventory is full"), ELogVerbosity::Warning,
                   EAutomationExpectedMessageFlags::Contains);

除非使用 …Plain 变体(AddExpectedMessagePlain、AddExpectedErrorPlain),否则模式会被视为正则表达式。AddExpectedError(Pattern, MatchType, Occurrences, bIsRegex) 是匹配警告和错误的简写形式(它会传递 ELogVerbosity::Warning,因此包含该级别);不带详细级别参数的 AddExpectedMessage 重载会匹配所有严重级别。

潜在命令

RunTest 会在潜在命令运行前返回。先将它们加入队列,然后在步骤完成时让 Update() 返回 true。在 return true; 之后加入队列属于不可达代码。

DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FMyWaitSecondsCommand, float, Duration);
bool FMyWaitSecondsCommand::Update()
{
    return GetCurrentRunTime() >= Duration;
}

ADD_LATENT_AUTOMATION_COMMAND(FMyWaitSecondsCommand(2.0f));   // inside RunTest, before `return true;`

存在从 DEFINE_LATENT_AUTOMATION_COMMAND 到 ..._FIVE_PARAMETER 的宏。若要在等待结束后进行断言,请派生自 IAutomationLatentCommand 并捕获 FAutomationTestBase*。

规格测试

BEGIN_DEFINE_SPEC(TClass, PrettyName, TFlags) / 成员声明 / END_DEFINE_SPEC(TClass) 用于声明一个 BDD 风格测试,其主体为 void TClass::Define()。在 Define() 中使用 Describe、It、LatentIt、BeforeEach、LatentBeforeEach、AfterEach(以及 xIt/xDescribe 来禁用代码块)。如果规格测试不需要成员,可用 DEFINE_SPEC(TClass, PrettyName, TFlags) 替代这对宏。references/automation-test-patterns.md 中有完整示例。

CQTest

CQTest(模块 CQTest)是基于 fixture 的前端,底层使用同一框架。Assert 是一个 FNoDiscardAsserter,因此所有断言都通过 ASSERT_THAT 执行,失败时会从方法返回。

#include "CQTest.h"

TEST_CLASS(FMyInventoryTests, "MyGame.Inventory")
{
    UMyInventoryComponent* Inventory = nullptr;

    BEFORE_EACH() { Inventory = NewObject<UMyInventoryComponent>(GetTransientPackage()); }

    TEST_METHOD(AddItem_WhenEmpty_StoresOne)
    {
        Inventory->AddItem(FName("Sword"), 1);
        ASSERT_THAT(AreEqual(1, Inventory->GetItemCount(FName("Sword"))));
    }

    TEST_METHOD_WITH_TAGS(AddItem_WhenFull_Rejects, "[Slow][Inventory]")
    {
        Inventory->SetCapacity(0);
        ASSERT_THAT(IsFalse(Inventory->AddItem(FName("Sword"), 1)));
    }
};

TEST_CLASS(ClassName, TestDir) 使用 FNoDiscardAsserter 和 DefaultFlags(EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::ProductFilter)。变体包括:TEST_CLASS_WITH_FLAGS(ClassName, TestDir, Flags)、TEST_CLASS_WITH_TAGS、TEST_CLASS_WITH_BASE、TEST_CLASS_WITH_ASSERTS。生命周期宏为 BEFORE_ALL()/AFTER_ALL()(静态方法,每个 fixture 执行一次)和 BEFORE_EACH()/AFTER_EACH()(重写 Setup()/TearDown())。断言器辅助方法:IsTrue、IsFalse、IsNull、IsNotNull、AreEqual、AreNotEqual、AreEqualIgnoreCase、AreNotEqualIgnoreCase、IsNear(Expected, Actual, Epsilon)——每个方法还都接受一个末尾的失败消息参数。

世界、Actor 和异步辅助工具(TestCommandBuilder、FSpawnHelper、FActorTestSpawner、FMapTestSpawner、TObjectBuilder、UTestGameInstance)详见 references/automation-test-patterns.md。

功能测试

AFunctionalTest(模块 FunctionalTesting)是放置在测试地图中的一个 Actor。PrepareTest、StartTest、IsReady_Implementation 和 OnTimeout 是 受保护的虚方法;FinishTest 和 Assert* 辅助方法是公有的。

// MyFunctionalTest.h
#pragma once
#include "CoreMinimal.h"
#include "FunctionalTest.h"
#include "MyFunctionalTest.generated.h"

UCLASS()
class MYGAME_API AMyFunctionalTest : public AFunctionalTest
{
    GENERATED_BODY()

protected:
    // PrepareTest() sets TimeLimit / PreparationTimeLimit; IsReady_Implementation()
    // gates StartTest(); StartTest() asserts and calls FinishTest().
    virtual void PrepareTest() override;
    virtual bool IsReady_Implementation() override;
    virtual void StartTest() override;

private:
    UPROPERTY(EditInstanceOnly, Category = "MyGame")
    TObjectPtr<AActor> TargetActor;
};

每个重写方法都先调用 Super::。FinishTest(EFunctionalTestResult TestResult, const FString& Message) 会结束运行;EFunctionalTestResult 的取值为 Default、Invalid、Error、Running、Failed、Succeeded,其中 Default 会让记录的断言决定结果。断言包括 AssertTrue/AssertFalse(bool Condition, const FString& Message, const UObject* ContextObject = nullptr)、AssertIsValid(UObject*, const FString&, const UObject* = nullptr)、AssertEqual_Int/Float/Bool/Name/Object/Vector/Rotator/Transform/Quat 和 AssertValue_Int/Float/Double/DateTime(带有 EComparisonMethod)。此外还有 TimeLimit、PreparationTimeLimit、SetTimeLimit(float, EFunctionalTestResult)、LogMessage(const FString&) 以及 StartStep/FinishStep。完整的 .cpp 示例见 references/automation-test-patterns.md。

使用蓝图节点 UFunctionalTestingManager::RunAllFunctionalTests(WorldContextObject, bNewLog, bRunLooped, FailedTestsReproString) 运行当前世界中的所有功能测试(该类为 MinimalAPI,且该函数没有 FUNCTIONALTESTING_API,因此从其他模块的 C++ 代码中调用时无法链接),也可以从命令行运行:

UnrealEditor-Cmd.exe MyGame.uproject -ExecCmds="Automation RunTests MyGame.Functional" -unattended -nopause -testexit="Automation Test Queue Empty" -log

Automation 还接受 List、RunTest、RunFilter <Filter>、RunAll、SetFilter、SetPriority、SetMinimumPriority、Quit 和 SoftQuit;用 ; 将它们串联。

性能分析

完整的命令表、CSV 和 LLM 工作流以及瓶颈排查流程见 references/profiling-commands.md。

Unreal Insights
# Capture at launch
UnrealEditor.exe MyGame.uproject -trace=cpu,frame,bookmark,counters,gpu,loadtime,log -tracehost=127.0.0.1
UnrealEditor.exe MyGame.uproject -trace=cpu,frame -tracefile=MyCapture.utrace

# Or from the console, mid-session
Trace.File D:/Captures/Run1.utrace cpu,frame,bookmark    # or Trace.Send 127.0.0.1 cpu,frame
Trace.Enable counters
Trace.Bookmark WaveStarted
Trace.Stop                    # also Trace.Pause, Trace.Resume, Trace.Status

通道名称去掉 Channel 后缀并改为小写:cpu、gpu、frame、bookmark、counters、log、loadtime、task、memalloc、callstack、module、metadata、net、rhicommands、rendercommands、slate、assetmetadata、iostore、animation、region、screenshot。

代码插桩
#include "Stats/Stats.h"                              // DECLARE_*_STAT, SCOPE_CYCLE_COUNTER
#include "HAL/PlatformMisc.h"                         // SCOPED_NAMED_EVENT
#include "ProfilingDebugging/CpuProfilerTrace.h"      // TRACE_CPUPROFILER_EVENT_SCOPE
#include "ProfilingDebugging/MiscTrace.h"             // TRACE_BOOKMARK
#include "ProfilingDebugging/CountersTrace.h"         // TRACE_*_COUNTER, TRACE_INT_VALUE
#include "ProfilingDebugging/CsvProfiler.h"           // CSV_*
#include "ProfilingDebugging/ScopedTimers.h"          // FScopedDurationTimer

DECLARE_STATS_GROUP(TEXT("MyGame"), STATGROUP_MyGame, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("MyGame Tick"), STAT_MyGameTick, STATGROUP_MyGame);
CSV_DEFINE_CATEGORY(MyGame, true);
TRACE_DECLARE_INT_COUNTER(MyGameActiveEnemies, TEXT("MyGame/ActiveEnemies"));

void UMySubsystem::Tick(float DeltaTime)
{
    SCOPE_CYCLE_COUNTER(STAT_MyGameTick);              // stat system + Insights
    TRACE_CPUPROFILER_EVENT_SCOPE(UMySubsystem::Tick); // Insights only, very cheap
    CSV_SCOPED_TIMING_STAT(MyGame, SubsystemTick);     // one CSV column
    SCOPED_NAMED_EVENT(MyGame_Tick, FColor::Orange);   // also visible to PIX/Razor

    TRACE_COUNTER_SET(MyGameActiveEnemies, ActiveEnemyCount);
    CSV_CUSTOM_STAT(MyGame, Enemies, ActiveEnemyCount, ECsvCustomStatOp::Set);
}

void UMySubsystem::BeginWave(int32 WaveNumber)
{
    TRACE_BOOKMARK(TEXT("WaveStarted_%d"), WaveNumber);  // vertical marker in Insights
    CSV_EVENT(MyGame, TEXT("WaveStarted"));

    double LoadSeconds = 0.0;
    {
        FScopedDurationTimer Timer(LoadSeconds);        // accumulates into LoadSeconds
        LoadWaveAssets(WaveNumber);
    }
    UE_LOGFMT(LogMyGame, Display, "Wave {Wave} loaded in {Seconds}s",
        ("Wave", WaveNumber), ("Seconds", LoadSeconds));
}

QUICK_SCOPE_CYCLE_COUNTER(STAT_MyAdHocScope) 会内联声明一个用于一次性测量的统计项;TRACE_INT_VALUE(TEXT("MyGame/Spawned"), Count) 和 TRACE_FLOAT_VALUE 无需声明即可发出计数器;SCOPED_NAMED_EVENT_TEXT("Name", FColor::Yellow) 接受字面值,而 SCOPED_NAMED_EVENT_F 会对其进行格式化。

stat 命令

首先运行 stat unit:它会将帧时间拆分为 Game、Draw、RHIT 和 GPU。然后用 stat game、stat scenerendering、stat initviews、stat slate、stat threading、stat streaming、stat net 深入查看占主导地位的线程,或者用 stat MyGame 查看自己的组。stat unitgraph 会绘制这些数值随时间变化的图表,stat fps 会显示帧率,stat hitches 会标记卡顿尖峰,stat namedevents 会将统计项名称作为分析器事件发出,供外部工具使用。

内存
stat memory / stat memoryplatform    # engine categories / OS physical and virtual
memreport -full                      # dump to Saved/Profiling/MemReports
obj list class=Texture2D             # loaded objects of a class, with sizes

使用 -LLM 和 -trace=memalloc,callstack,module 启动,以使用 Memory Insights;使用 LLM_SCOPE(ELLMTag::EngineMisc) 为分配划定作用域,或通过 LLM_DEFINE_TAG/LLM_DECLARE_TAG 声明自定义标签,并使用 LLM_SCOPE_BYTAG 进入该作用域。在 Windows 上,非编辑器构建默认使用 MallocBinned3;可通过 -binnedmalloc2、-binnedmalloc、-ansimalloc 或 -stompmalloc 覆盖。

卡顿捕获与 UObject 计数

snapshothitches -start 会启用自动跟踪快照:每当统计系统检测到卡顿时便会捕获快照;snapshothitches -stop 会将其停用。此功能需要启用 STATS,且不能用于 Shipping 构建;启用期间会禁用 Screenshot 通道,以防过期截图进入快照尾部。

启用 CSV 分析后,使用 CSV_TRACK_UOBJECT_COUNT=1 编译会逐帧记录存活的 UObject 数量,作为类别 ObjectCount 中的 CSV 统计项 Total;该数值来自 UObjectStats::GetUObjectCount()。可利用此功能在长时间会话中检测对象泄漏,而无需进行完整的内存捕获。

调试可视化

调试绘制

DrawDebugHelpers.h 中的所有内容都受 ENABLE_DRAW_DEBUG 控制,其值为 UE_ENABLE_DEBUG_DRAWING = (!(UE_BUILD_SHIPPING || UE_BUILD_TEST) || WITH_EDITOR)。在 Test 或 Shipping 游戏构建中,它们会变为空的内联存根(DrawDebugHelpers.h:180-185;定义 SHIPPING_DRAW_DEBUG_ERROR=1 会移除这些存根,使残留调用无法通过编译),但参数仍会求值——请用 #if ENABLE_DRAW_DEBUG 包裹开销较大的调用位置。

#include "DrawDebugHelpers.h"

// LifeTime: -1 (default) or 0 draws one frame; > 0 is seconds.
// bPersistentLines = true keeps the shape until FlushPersistentDebugLines.
UWorld* World = GetWorld();
DrawDebugLine(World, Start, End, FColor::Red, false, 2.0f, 0, 2.0f);
DrawDebugPoint(World, Location, 8.0f, FColor::White, false, 2.0f, 0);
DrawDebugDirectionalArrow(World, Start, End, 40.0f, FColor::Cyan, false, 2.0f, 0, 2.0f);
DrawDebugBox(World, Center, Extent, FQuat::Identity, FColor::Blue, false, 2.0f, 0, 1.0f);
DrawDebugSphere(World, Center, 100.0f, 12, FColor::Green, false, 2.0f, 0, 1.0f);
DrawDebugCapsule(World, Center, 88.0f, 34.0f, FQuat::Identity, FColor::Yellow, false, 2.0f, 0, 1.0f);
DrawDebugString(World, Location, TEXT("Label"), nullptr, FColor::White, 2.0f, true, 1.0f);
FlushPersistentDebugLines(World);   // and FlushDebugStrings(World)

面向 Blueprint 的 UKismetSystemLibrary::DrawDebugLine/Arrow/Sphere/Capsule/String 会自行进行条件判断,接受 FLinearColor 和 EDrawDebugSceneDepthPriorityGroup;此外还有 PrintString(WorldContextObject, InString, bPrintToScreen, bPrintToLog, TextColor, Duration, Key)。

Visual Logger

ENABLE_VISUAL_LOG 的值为 PLATFORM_DESKTOP && !NO_LOGGING && UE_ENABLE_DEBUG_DRAWING。通过 窗口 > Visual Logger 打开时间线。

#include "VisualLogger/VisualLogger.h"

UE_VLOG(this, LogMyGame, Log, TEXT("State: %s"), *StateName);
UE_VLOG_UELOG(this, LogMyGame, Warning, TEXT("Lost target %s"), *TargetName);  // also hits UE_LOG
UE_VLOG_LOCATION(this, LogMyGame, Verbose, GetActorLocation(), 20.0f, FColor::Green, TEXT("Here"));
UE_VLOG_SEGMENT(this, LogMyGame, Log, PathStart, PathEnd, FColor::Red, TEXT("Path"));
UE_VLOG_SPHERE(this, LogMyGame, Verbose, PatrolPoint, 50.0f, FColor::Blue, TEXT("Patrol"));
UE_VLOG_ARROW(this, LogMyGame, Log, GetActorLocation(), AimPoint, FColor::Yellow, TEXT("Aim"));  // also _BOX, _CAPSULE
UE_CVLOG(bIsAlerted, this, LogMyGame, Log, TEXT("Alerted"));   // also UE_CVLOG_SEGMENT, UE_CVLOG_LOCATION

FVisualLogger::Get().SetIsRecording(true);       // and SetIsRecordingToFile / SetIsRecordingToTrace

每个宏都会先检查 FVisualLogger::IsRecording(),因此关闭记录时,参数不会产生任何开销。

Gameplay Debugger 和 ShowDebug

在 PIE 中按 '。在模块的 StartupModule 中注册一个类别(将 "GameplayDebugger" 添加到 PrivateDependencyModuleNames;注册操作和类别类都用 #if WITH_GAMEPLAY_DEBUGGER 包裹。默认情况下,该宏在 Test/Shipping 游戏构建中为 0 — GameplayDebuggerCategory.h:10-11、TargetRules.cs:1125):

#include "GameplayDebugger.h"   // in FMyGameModule::StartupModule()
if (IGameplayDebugger::IsAvailable())
{
    IGameplayDebugger& Debugger = IGameplayDebugger::Get();
    Debugger.RegisterCategory(TEXT("MyGame"),
        IGameplayDebugger::FOnGetCategory::CreateStatic(&FMyDebuggerCategory::MakeInstance),
        EGameplayDebuggerCategoryState::EnabledInGame);
    Debugger.NotifyCategoriesChanged();
}

FMyDebuggerCategory 派生自 FGameplayDebuggerCategory,并重写 virtual void CollectData(APlayerController* OwnerPC, AActor* DebugActor) 和 virtual void DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext),通过 AddTextLine 和 AddShape 为它们提供数据。在 ShutdownModule 中调用 UnregisterCategory(TEXT("MyGame"))。

AHUD::ShowDebug(FName DebugType) 可切换指定的调试页面(ShowDebug AI、ShowDebug Physics);ShowDebugToggleSubCategory(FName) 可切换子页面,ShowDebugForReticleTargetToggle(TSubclassOf<AActor>) 则将其目标切换为准星所指向的对象。重写 virtual void ShowDebugInfo(float& YL, float& YPos) 或绑定 AHUD::OnShowDebugInfo,以实现自己的页面。独立的 display、displayall 和 displayclear 控制台命令可将对象或类的指定属性打印到 HUD 上。

控制台命令和 CVar

// In the AMyPlayerController class body. Exec works on PlayerController, Pawn, HUD,
// GameMode, GameState, CheatManager (or a UCheatManagerExtension) and GameInstance.
UFUNCTION(Exec)
void ToggleGodMode();
// MyGameDebug.cpp — console objects register at static init
#include "HAL/IConsoleManager.h"

static FAutoConsoleCommand GMyDumpStatsCmd(
    TEXT("MyGame.DumpStats"), TEXT("Dump gameplay stats"),
    FConsoleCommandDelegate::CreateStatic(&FMyStatsDumper::Dump));

// World + args; prefer this for anything that touches gameplay in PIE.
static FAutoConsoleCommandWithWorldAndArgs GMySpawnItemCmd(
    TEXT("MyGame.SpawnItem"), TEXT("Usage: MyGame.SpawnItem <Name>"),
    FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&FMySpawner::SpawnFromConsole));

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

// FAutoConsoleVariableRef binds a console name to an existing int32/float/bool/FString/FName.
static float GMyDrawDistance = 5000.0f;
static FAutoConsoleVariableRef CVarMyDrawDistance(
    TEXT("MyGame.DrawDistance"), GMyDrawDistance, TEXT("Gameplay draw distance."), ECVF_Default);

使用 GetValueOnGameThread()、GetValueOnRenderThread() 或 GetValueOnAnyThread() 读取 CVar。常用标志:ECVF_Default、ECVF_Cheat(在 Shipping 目标中禁用)、ECVF_ReadOnly、ECVF_Scalability、ECVF_RenderThreadSafe。当命令的生命周期与某个对象绑定时,请在运行时使用 IConsoleManager::Get().RegisterConsoleCommand(Name, Help, Delegate) 注册,并使用 IConsoleManager::Get().UnregisterConsoleObject(Cmd) 释放返回的 IConsoleCommand*。

已弃用 — 请勿使用

不要输出在 5.8 中使用来源
UE_TRACE_BOOKMARK(...)TRACE_BOOKMARK(Format, ...)ProfilingDebugging/MiscTrace.h — 不存在 UE_TRACE_BOOKMARK
Trace.Start [Channels]Trace.File [Path] [Channels]TraceAuxiliary.cpp 中说明了“'Trace.Start' is being deprecated in favor of 'Trace.File'”
stat startfile / stat stopfile / stat startfileraw(.uestats)Trace.File + UnrealInsights.exe在 Stats/StatsFile.h 中受 UE_ENABLE_STATS_FILE_DEPRECATED_IN_5_8(默认值为 0)控制
stat gpuProfileGPU、DumpGPU 或按队列划分的组 stat GPU0_Graphics0RHI/Private/GPUProfiler.cpp 中的 GPU 组由 GPU%d_%s%d 构建为 STATGROUP_%s
未限定作用域的标志名称,如 EditorContext为其指定作用域:EAutomationTestFlags::EditorContextEAutomationTestFlags 在 Misc/AutomationTest.h 中是 enum class
将 EAutomationExpectedErrorFlags::… 作为独立类型使用EAutomationExpectedMessageFlags::…前者现在是后者的命名空间别名
UE_LOG_REF、UE_LOG_CLINKAGEUE_LOG在 Logging/LogMacros.h 中标记为“DO NOT USE … will be deprecated”
-csvstatfile=Out.csv-csvCaptureFrames=N 加上 CsvProfile StartFile=<name>CsvProfiler.cpp 会解析 csvCaptureFrames=、csvCategories=、csvMetadata=、csvGpuStats、csvExecCmds= 等,但不解析 csvstatfile

常见错误

以为 Test 构建会保留 check/ensure: 在 Test 和 Shipping 中,DO_CHECK 和 DO_ENSURE 分别对应 USE_CHECKS_IN_SHIPPING / USE_ENSURES_IN_SHIPPING,且两者默认值均为 0。如果表达式必须始终执行,请使用 verify(),或设置 bUseChecksInShipping。

在日志或断言中执行逻辑: UE_LOG(LogMyGame, Log, TEXT("%d"), AdvanceCounter()); 在启用 NO_LOGGING 后就不再递增计数器,而 check(Init()) 在 DO_CHECK 为 0 后就不再初始化。先将结果赋给局部变量,再对其记录日志或进行断言。

使用 Log 却期待控制台输出: Display 会将内容输出到控制台;Log 只会写入日志文件。“我的日志行怎么没出现”几乎总是因为使用了 Log 级别的消息。

缺少筛选标志: 每个 IMPLEMENT_*_AUTOMATION_TEST、DEFINE_SPEC 和 BEGIN_DEFINE_SPEC 都必须且只能指定 SmokeFilter/EngineFilter/ProductFilter/PerfFilter/StressFilter/NegativeFilter 中的一个,并至少指定一个上下文标志,否则 static_assert 会导致编译失败。

丢弃 CQTest 断言结果: 断言器方法带有 [[nodiscard]],因此 Assert.IsTrue(x); 会发出警告,并在失败后继续运行。务必写成 ASSERT_THAT(IsTrue(x));。

在 Shipping 或 Test 游戏构建中使用 DrawDebug*: 这些调用会变成空操作存根,但其参数(追踪、字符串格式化)仍会执行。请将整个代码块包在 #if ENABLE_DRAW_DEBUG 中。

跳过 Super::StartTest(): AFunctionalTest::StartTest 会触发 Blueprint ReceiveStartTest 事件,因此省略 Super:: 会导致同一测试 Actor 中由 Blueprint 编写的所有步骤都被静默禁用。

相关技能

  • ue-cpp-foundations — UObject 基础知识、委托、容器、UFUNCTION/UPROPERTY 说明符表
  • ue-module-build-system — Build.cs 和 Target.cs 配置、bBuildDeveloperTools、测试模块设置、插件布局
  • ue-editor-tools — 承载测试和调试 UI 的编辑器实用工具、命令let 和 Slate 工具
  • ue-physics-collision — 可使用 DrawDebug* 可视化结果的追踪和扫描查询
  • ue-ai-navigation — 行为树、EQS 和 AI 端 Gameplay Debugger 类别
  • ue-async-threading — 潜在命令和 task 追踪通道可观测的任务图和异步工作
  • ue-gameplay-framework — GameMode、PlayerController、HUD 和 CheatManager,即 Exec 函数的承载对象
  • ue-materials-rendering — 材质实例、参数集合、渲染目标和后期处理

评论

支持 Markdown