Loading...
Loading...
Compare original and translation side by side
.agents/ue-project-context.md.agents/ue-project-context.mdMyGameTests"AutomationController"ExtraModuleNamesif (bWithAutomationTests)MyGameTests"AutomationController"ExtraModuleNamesif (bWithAutomationTests)// 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;
}// 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;
}IMPLEMENT_COMPLEX_AUTOMATION_TESTGetTests()RunTest(Parameters)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)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;
}// 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"));// 相等断言 — 支持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"));| 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代码提交时运行 |
| 项目/游戏级测试 |
Update()truefalseDEFINE_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));references/automation-test-patterns.mdUpdate()truefalseDEFINE_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.mdAFunctionalTestUCLASSStartTest()FinishTest()// 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"));
}Maps/Test_MyFeature.umapRunAutomationTest "MyGame.Functional.MyFeature"AFunctionalTestUCLASSStartTest()FinishTest()// 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"));
}Maps/Test_MyFeature.umapRunAutomationTest "MyGame.Functional.MyFeature"// Timeout — auto-fail if test exceeds time limit
void AMyFunctionalTest::PrepareTest()
{
Super::PrepareTest();
TimeLimit = 10.0f; // seconds; 0 = no timeout (default)
}// 超时设置 — 若测试超出时间限制则自动失败
void AMyFunctionalTest::PrepareTest()
{
Super::PrepareTest();
TimeLimit = 10.0f; // 秒;0表示无超时(默认值)
}// 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);// 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(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());// 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());| 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 |
| 级别 | 使用场景 |
|---|---|
| 不可恢复的致命错误,触发崩溃 |
| 操作失败,需要开发者关注 |
| 意外但可恢复的情况 |
| 用户可见的输出(始终显示) |
| 标准开发信息 |
| 详细的每帧或每次调用信息 |
| 跟踪级信息;频率极高 |
#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));#include "Logging/StructuredLog.h"
// 命名字段 — 顺序无关;允许格式字符串之外的额外字段
UE_LOGFMT(LogMyModule, Warning,
"Loading '{Name}' failed with error {Error}",
("Name", AssetName), ("Error", ErrorCode), ("Flags", LoadFlags));undefinedundefined
---
---Misc/AssertionMacros.hMisc/AssertionMacros.h// 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();undefined// 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"));// 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);| 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 |
DrawDebugHelpers.h#if ENABLE_DRAW_DEBUG#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_DEBUGundefined// 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"));
}PlayerControllerPawnHUDGameModeGameStateCheatManagerGameInstanceundefined// 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);// 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);FAutoConsoleCommandRegisterConsoleCommand// 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); }undefined#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);undefined'IGameplayDebugger::Get().RegisterCategory("MySystem",
IGameplayDebugger::FOnGetCategory::CreateStatic(&FMyDebugCategory::MakeInstance),
EGameplayDebuggerCategoryState::EnabledInGame);undefinedUnrealEditorDebugGameDebugDevelopment// 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 hitcheck()ensure()| 场景 | 宏 |
|---|---|
| 类不变量、程序员错误 | |
| 可恢复情况,需继续执行 | |
| 表达式包含必须始终执行的副作用 | |
| Debug构建中的重度验证 | |
| 用户输入验证 | 无 — 使用显式if/return逻辑 |
Saved/Crashes/UnrealEditor-Win64-DebugGameFDebug::DumpStackTraceToLog(ELogVerbosity::Error)ensure-NetTracestat netnet.ListActorChannelsstat startfilestat stopfilestat gpuProfileGPUstat memoryplatformmemreport -full-trace=cpu,gpu,frame,memoryreferences/profiling-commands.mdDrawDebugHelpers.h#if ENABLE_DRAW_DEBUG#include "DrawDebugHelpers.h"check()verify()ensureAlwaysENABLE_DRAW_DEBUG#if ENABLE_DRAW_DEBUGUE_LOGIMPLEMENT_SIMPLE_AUTOMATION_TESTstatic_assertreturn truereturn true;GetWorld()->GetNetMode()UE_CLOG(HasAuthority(), ...)ue-cpp-foundationsue-module-build-systemue-actor-component-architecturereferences/automation-test-patterns.mdreferences/profiling-commands.md
---// 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"));
}PlayerControllerPawnHUDGameModeGameStateCheatManagerGameInstance// 静态初始化时注册;无需世界上下文
static FAutoConsoleCommand GDumpStatsCmd(
TEXT("MyGame.DumpStats"), TEXT("Dump gameplay stats"),
FConsoleCommandDelegate::CreateLambda([]()
{
UE_LOG(LogMyModule, Display, TEXT("=== GameStats ==="));
}));undefinedundefined
与`FAutoConsoleCommand`(静态初始化时注册)不同,`RegisterConsoleCommand`在运行时注册并返回句柄用于显式清理。
---undefined
---#include "VisualLogger/VisualLogger.h"undefined'IGameplayDebugger::Get().RegisterCategory("MySystem",
IGameplayDebugger::FOnGetCategory::CreateStatic(&FMyDebugCategory::MakeInstance),
EGameplayDebuggerCategoryState::EnabledInGame);UnrealEditorDebugGameDebugDevelopmentundefined
对于`check()`和`ensure()`失败,在上述处理函数上设置断点 — 它们会在崩溃/日志前触发,让您可以检查调用栈。Saved/Crashes/UnrealEditor-Win64-DebugGameFDebug::DumpStackTraceToLog(ELogVerbosity::Error)ensure-NetTracestat netnet.ListActorChannelsstat startfilestat stopfilestat gpuProfileGPUstat memoryplatformmemreport -full-trace=cpu,gpu,frame,memoryreferences/profiling-commands.mdcheck()verify()ensureAlwaysENABLE_DRAW_DEBUG#if ENABLE_DRAW_DEBUGUE_LOGIMPLEMENT_SIMPLE_AUTOMATION_TESTstatic_assertreturn true;GetWorld()->GetNetMode()UE_CLOG(HasAuthority(), ...)ue-cpp-foundationsue-module-build-systemue-actor-component-architecturereferences/automation-test-patterns.mdreferences/profiling-commands.md