ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

UE5 C++开发必备:UE_LOG日志系统7大实战技巧与性能优化

UE5 C++开发必备:UE_LOG日志系统7大实战技巧与性能优化 1. 项目概述为什么UE_LOG是UE5 C开发的“第一课”如果你刚开始接触UE5的C开发面对动辄几十万行的引擎源码和复杂的运行时行为是不是经常感觉像在“盲人摸象”代码写完了编译通过了但运行时角色没动、特效没播、数据没存问题出在哪是蓝图节点没连对还是C逻辑写错了这时候一个最原始但最有效的方法就是“打印日志”。在UE5的C世界里UE_LOG宏就是你的“调试之眼”。它远不止是简单的printf或std::cout而是一个集成了分类、级别、格式化、上下文追踪等强大功能的日志系统。掌握它意味着你能在程序运行的任何时刻清晰地看到代码的执行路径、变量的实时状态从而快速定位问题。很多新手觉得日志打印太基础但恰恰是这项基础技能决定了你排查Bug的效率上限。本文将抛开教科书式的简单介绍直接切入实战分享7种在真实项目开发中高频使用的UE_LOG技巧并附上可直接“抄作业”的代码示例帮你从“会打日志”进阶到“打好日志”。2. UE_LOG核心机制与基础用法深度解析在深入技巧之前我们必须理解UE_LOG的运作基石。它不是一个简单的函数而是一个高度宏展开的复杂系统其设计充分考虑了性能在开发版本与发布版本中的差异、灵活性多种日志类别和级别以及与引擎其他子系统如输出日志窗口、文件记录、网络控制台的集成。2.1 日志类别Log Category管理日志的“命名空间”这是UE_LOG第一个关键参数。它的作用是将日志分门别类方便你在海量输出中过滤和筛选特定模块的日志。如何声明一个日志类别通常在每个模块的.cpp文件顶部进行声明和定义。// 在YourModule.cpp文件中 DEFINE_LOG_CATEGORY_STATIC(LogYourGame, Log, All);DEFINE_LOG_CATEGORY_STATIC: 这是一个宏用于定义一个仅在当前编译单元即当前.cpp文件内可见的静态日志类别。对于游戏逻辑模块这通常是推荐做法。LogYourGame: 你自定义的日志类别名。惯例是以Log开头后面跟上你的项目或模块名例如LogMyGame,LogInventorySystem。Log: 默认的日志级别。这里指的是这个类别的默认“冗余度”级别Log表示默认输出所有Log级别及更重要的日志。All: 表示在开发版本中该类别默认启用所有级别的日志输出。为什么需要自定义类别想象一下你的游戏同时运行着AI系统、物理系统、动画系统和UI系统。如果所有日志都使用默认的LogTemp类别当AI出现寻路问题时你需要从成千上万条混杂的物理碰撞、UI点击日志中费力寻找。而如果你为AI模块定义了LogAISystem只需在编辑器的“输出日志”窗口或命令行中过滤LogAISystem就能瞬间聚焦所有相关日志效率提升巨大。2.2 日志级别Log Verbosity区分信息的“紧急程度”第二个关键参数是日志级别它决定了日志的重要性。UE5预定义了多个级别按严重性从高到低排列Fatal: 致命错误。打印日志后程序会立即崩溃check失败时内部使用。切勿在常规游戏逻辑中使用。Error: 错误。表示发生了不可恢复的错误但程序可能尝试继续运行。例如“加载资源失败”。Warning: 警告。表示可能存在潜在问题或非预期状态但当前逻辑仍可继续。例如“传入了一个空指针但已使用默认值处理”。Log: 常规信息。用于报告正常的程序流程和状态。这是最常用的级别。例如“玩家进入区域A”、“物品X被拾取”。Verbose: 详细信息。用于输出更细致的调试信息在一般调试时可能不需要看到。例如“每帧更新角色的位置向量当前值为 (X, Y, Z)”。VeryVerbose: 非常详细的信息。用于输出海量的、极其细致的跟踪信息通常只在深入排查特定问题时才启用。级别的核心作用在于过滤控制。你可以在项目设置、命令行参数或运行时控制台中动态调整每个日志类别输出的最低级别。例如在开发初期你可以将LogYourGame的级别设为Verbose以查看所有细节而在测试版本中你可能只关心Warning及以上级别的问题这时就可以将级别调高避免日志输出过于频繁影响性能尤其是在打包后低级别日志的调用开销仍存在。2.3 基础格式与参数UE_LOG的基本格式如下UE_LOG(LogCategory, Verbosity, Format, ...)Format: 一个格式字符串类似于printf。...: 可变参数对应格式字符串中的占位符。UE5使用自己的一套格式化占位符与标准C库略有不同更安全且支持更多引擎类型%s: 字符串 (TCHAR*,FString)%d,%i: 有符号整数 (int32)%u: 无符号整数 (uint32)%f: 浮点数 (float)%lld: 64位有符号整数 (int64)%llu: 64位无符号整数 (uint64)%x,%X: 十六进制整数%p: 指针%s对于FString: 需要调用*FString或使用TCHAR_TO_ANSI。更推荐使用TEXT宏包裹字符串直接嵌入。一个完整的简单示例// 假设在MyActor.cpp中已定义 DEFINE_LOG_CATEGORY_STATIC(LogMyGame, Log, All); void AMyActor::BeginPlay() { Super::BeginPlay(); FString PlayerName TEXT(RookieDev); int32 Score 100; float Health 75.5f; UE_LOG(LogMyGame, Log, TEXT(Actor %s has begun play. Initial Score: %d, Health: %.1f), *GetName(), Score, Health); }输出可能类似于LogMyGame: Actor MyActor_1 has begun play. Initial Score: 100, Health: 75.5注意性能考量。UE_LOG在开发编辑器模式下开销较小但在打包后的非开发版本中即使日志因级别过滤未被输出格式化参数的计算和函数调用开销依然存在。对于在频繁调用的函数如Tick中的低级别(Verbose,VeryVerbose)日志建议使用UE_LOG的IS_SUPPRESSED宏或条件编译进行包裹以避免不必要的性能损失。3. 7种提升调试效率的UE_LOG实战技巧理解了基础我们进入实战环节。下面这7个技巧是我在多个UE项目中实际踩坑后总结出的高效调试方法。3.1 技巧一结构化输出与复杂对象打印直接拼接字符串的方式在打印复杂信息时难以阅读。我们可以利用格式字符串进行对齐和结构化。// 打印一个物品信息表 FString ItemName TEXT(传奇宝剑); int32 ItemID 1001; float DamageValue 42.8f; FString Rarity TEXT(史诗); // 糟糕的写法可读性差 UE_LOG(LogMyGame, Log, TEXT(Item: %s, ID:%d, Damage:%f, Rarity:%s), *ItemName, ItemID, DamageValue, *Rarity); // 推荐的写法使用格式对齐 UE_LOG(LogMyGame, Log, TEXT(| %-20s | %6d | %8.1f | %-10s |), *ItemName, ItemID, DamageValue, *Rarity); // 输出类似表格| 传奇宝剑 | 1001 | 42.8 | 史诗 |对于引擎内建的复杂类型如FVector、FRotator、FTransform它们通常重载了ToString()方法或可以直接用于格式化。FVector Location GetActorLocation(); FRotator Rotation GetActorRotation(); UE_LOG(LogMyGame, Verbose, TEXT(Actor Location: %s, Rotation: %s), *Location.ToString(), *Rotation.ToString()); // 输出Actor Location: X120.0 Y340.5 Z10.0, Rotation: Pitch0.0 Yaw90.0 Roll0.0实操心得对于自定义的USTRUCT或UCLASS重写其ToString()方法是一个好习惯。这样在日志中只需*MyStruct.ToString()就能打印出所有关键字段调试时一目了然。3.2 技巧二条件编译与开发/发布版本控制如前所述日志在发布版本中可能成为性能负担。我们需要确保调试日志不会出现在最终的产品中。方法A使用UE_LOG的级别和项目设置这是最基础的方法。在项目设置(Edit - Project Settings - Engine - Logging)中你可以为每个日志类别设置“发布版本”的默认级别。例如将LogMyGame的发布版本级别设为Warning那么所有Log和Verbose级别的日志在打包后就不会输出但其函数调用和参数计算依然会发生。方法B使用#if WITH_EDITOR或#if !UE_BUILD_SHIPPING这是更彻底的控制确保调试代码在发布版本中完全不被编译。void AMyActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); #if !UE_BUILD_SHIPPING // 或者 #if WITH_EDITOR // 这段日志代码在打包的Shipping版本中根本不存在 FVector TraceStart GetActorLocation(); FVector TraceEnd TraceStart GetActorForwardVector() * 1000.0f; UE_LOG(LogMyGame, VeryVerbose, TEXT(Tick Trace: Start%s, End%s), *TraceStart.ToString(), *TraceEnd.ToString()); #endif }方法C使用UE_LOG的日志抑制检查性能最优对于那些参数计算本身也有开销的日志可以使用UE_LOG宏内部的IS_SUPPRESSED机制。void AMyActor::ComplexCalculation() { // 假设CalculateDebugInfo()函数开销很大 FString DebugInfo CalculateDebugInfo(); // 只有在LogMyGame类别的Verbose级别日志未被抑制时才计算参数并调用日志 if (!UE_LOG_ACTIVE(LogMyGame, Verbose)) { // 如果日志被抑制提前返回避免计算DebugInfo return; } // 由于上面已经检查过这里可以安全地使用UE_LOG但通常我们会把计算放在里面 // 更好的写法是将条件判断和计算封装 }更优雅的写法是利用宏#define MY_VERBOSE_LOG(Category, Format, ...) \ do { \ if (UE_LOG_ACTIVE(Category, Verbose)) { \ UE_LOG(Category, Verbose, Format, ##__VA_ARGS__); \ } \ } while(0)然后使用MY_VERBOSE_LOG(LogMyGame, TEXT(“Info: %s”), *CalculateDebugInfo());。这样只有当Verbose日志确实需要输出时才会调用昂贵的CalculateDebugInfo()函数。重要选择对于Tick等高频调用中的调试日志强烈推荐使用方法C条件激活或方法B条件编译。方法A仅阻止了输出但函数调用和参数构造的开销仍在在性能敏感处可能成为瓶颈。3.3 技巧三使用FMsg与ELogVerbosity进行灵活记录UE_LOG是与引擎日志系统深度集成的。有时你可能需要更灵活地记录信息或者将日志输出到不同的地方如屏幕、文件。这时可以了解下FMsg命名空间。#include “Misc/OutputDevice.h” #include “Misc/OutputDeviceHelper.h” // 使用FMsg直接输出到当前活动的日志设备通常是输出日志窗口 FMsg::Logf(__FILE__, __LINE__, LogMyGame.GetCategoryName(), ELogVerbosity::Log, TEXT(“This is a direct log via FMsg: %d”), 123); // 它的一个实用场景是自定义日志输出目标 class FMyCustomOutputDevice : public FOutputDevice { public: virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName Category) override { // 你可以在这里将日志写入自定义文件、发送到网络、或显示在游戏内UI上 MyCustomFileWriter.Write(V); } }; // 在程序初始化时添加 GLog-AddOutputDevice(new FMyCustomOutputDevice());__FILE__和__LINE__宏能自动捕获日志调用处的文件名和行号对于定位问题非常有帮助。UE_LOG宏内部也使用了它们。实操心得在编写插件或独立模块时如果想提供一个不依赖UE_LOG宏的日志接口给用户使用FMsg::Logf是一个更底层、更灵活的选择。但绝大多数日常调试UE_LOG完全够用且更便捷。3.4 技巧四利用日志进行断言和错误检查断言Assert是防御性编程的重要手段。UE提供了check,verify,ensure等宏。结合UE_LOG我们可以提供更丰富的错误上下文。void UMyInventoryComponent::AddItem(FItemData NewItem) { // 使用check条件为false时在开发版本中触发严重错误并崩溃 check(NewItem.IsValid()); // 如果物品无效直接崩溃并附带默认错误信息 // 使用checkf可以附带自定义格式化的错误信息 checkf(NewItem.Quantity 0, TEXT(“Trying to add item ‘%s’ with invalid quantity %d”), *NewItem.Name, NewItem.Quantity); // 使用ensure条件为false时在开发版本中记录一次错误非致命程序继续运行。在发布版本中会被编译掉。 bool bSuccess InternalAddItem(NewItem); if (!ensureMsgf(bSuccess, TEXT(“Failed to add item ‘%s’ to inventory. Might be full.”), *NewItem.Name)) { // ensure失败后会执行到这里我们可以进行一些恢复操作 UE_LOG(LogMyGame, Error, TEXT(“Inventory add failed for item %s. Current count: %d”), *NewItem.Name, Items.Num()); TriggerInventoryFullEvent(); } }ensure系列宏在调试时极其有用。它允许你标记出“理论上不该发生但如果发生了我们也希望能知道并可能恢复”的情况。在编辑器中运行ensure失败会弹出一个带调用堆栈的对话框你可以选择“继续”来观察后续影响而不是像check那样直接中断。3.5 技巧五为蓝图暴露可调节的日志节点对于策划或美术同事他们可能使用蓝图。我们可以将日志功能封装成蓝图可调用的函数并加上参数让他们控制是否打印、打印级别等。// 在头文件(.h)中声明一个蓝图可调用的静态函数 UFUNCTION(BlueprintCallable, Category “Debug|Log”, meta (DevelopmentOnly)) // meta(DevelopmentOnly)使得该节点在打包后不可见 static void PrintDebugLog(const FString Message, ELogVerbosity::Type Verbosity ELogVerbosity::Log, bool bPrintToScreen true, float ScreenDuration 2.0f, FLinearColor TextColor FLinearColor::White); // 在源文件(.cpp)中实现 void UMyDebugLibrary::PrintDebugLog(const FString Message, ELogVerbosity::Type Verbosity, bool bPrintToScreen, float ScreenDuration, FLinearColor TextColor) { #if !UE_BUILD_SHIPPING // 1. 输出到日志系统 FMsg::Logf(__FILE__, __LINE__, LogMyGame.GetCategoryName(), Verbosity, TEXT(“%s”), *Message); // 2. 可选输出到游戏屏幕 if (bPrintToScreen GEngine) { GEngine-AddOnScreenDebugMessage(-1, ScreenDuration, TextColor.ToFColor(true), Message); } #endif }这样在蓝图中就可以直接调用这个节点输入想打印的信息并选择是否显示在屏幕上、显示什么颜色、持续多久。meta(DevelopmentOnly)确保了这些调试节点不会泄露到发布版本中。注意事项屏幕消息(AddOnScreenDebugMessage)非常方便但大量使用会严重干扰游戏画面。建议仅在关键流程或临时调试时开启并养成随时关闭的习惯。3.6 技巧六通过命令行参数动态控制日志输出在编辑器或打包后的游戏运行时你可以通过命令行参数实时调整日志的详细程度而无需重新编译代码。常用命令在编辑器输出日志窗口的输入框或启动游戏时加命令行参数-LogCmds”LogCategory Verbosity”: 设置特定日志类别的级别。例如-LogCmds”LogAISystem Verbose”将AI系统的日志级别设为Verbose。-LogCmds”LogPhysics Warning”将物理系统的日志级别设为Warning只显示警告及以上。-LogCmds”LogCategory Off”: 关闭特定类别的所有日志。-All打开所有日志类别的所有级别输出会产生巨量日志慎用。在编辑器输出日志窗口你也可以直接输入Log LogAISystem Verbose来达到同样效果。你可以在游戏代码中响应这些命令实现更动态的控制。例如当检测到特定命令行参数时开启某个子系统的详细性能分析日志。void AMyGameMode::BeginPlay() { Super::BeginPlay(); // 解析命令行看是否要开启性能分析日志 FString CmdLine FCommandLine::Get(); if (CmdLine.Contains(TEXT(“-ProfileAI”))) { // 激活AI系统的高详细度日志 UE_LOG(LogAISystem, Verbose, TEXT(“AI performance profiling enabled via command line.”)); bEnableAIDetailedProfiling true; } }3.7 技巧七创建自定义日志宏提升可读性与效率如果你发现自己在重复编写带有相同类别、级别和条件判断的日志语句创建一个自定义的日志宏可以大大简化代码。// 在公共头文件如MyGameUtils.h中定义宏 #pragma once // 用于常规游戏逻辑日志 #define MY_LOG(Format, ...) UE_LOG(LogMyGame, Log, Format, ##__VA_ARGS__) // 用于警告 #define MY_LOG_WARNING(Format, ...) UE_LOG(LogMyGame, Warning, Format, ##__VA_ARGS__) // 用于错误 #define MY_LOG_ERROR(Format, ...) UE_LOG(LogMyGame, Error, Format, ##__VA_ARGS__) // 带条件判断的详细日志性能优化版 #define MY_LOG_VERBOSE(Condition, Format, ...) \ do { \ if (Condition UE_LOG_ACTIVE(LogMyGame, Verbose)) { \ UE_LOG(LogMyGame, Verbose, Format, ##__VA_ARGS__); \ } \ } while(0) // 带函数名自动记录的日志 #define MY_LOG_FUNC(Verbosity, Format, ...) \ UE_LOG(LogMyGame, Verbosity, TEXT(“[%s] “) Format, TEXT(__FUNCTION__), ##__VA_ARGS__) // 使用示例 void AMyCharacter::PerformAttack() { MY_LOG_FUNC(Log, TEXT(“Starting attack.”)); if (!CurrentWeapon) { MY_LOG_ERROR(TEXT(“No weapon equipped!”)); return; } MY_LOG_VERBOSE(bShowAttackDebug, TEXT(“Attack direction: %s”), *GetAttackDirection().ToString()); // ... 攻击逻辑 MY_LOG(TEXT(“Attack performed. Damage: %.1f”), CalculatedDamage); }自定义宏的好处简洁MY_LOG(“Hello”)比UE_LOG(LogMyGame, Log, TEXT(“Hello”))更短。一致确保整个项目使用统一的日志类别和风格。增强像MY_LOG_FUNC可以自动添加函数名方便追踪执行流。封装将性能优化如条件检查封装在宏内部使用起来更安全。警告定义宏时要小心作用域和副作用。确保宏参数被正确展开避免出现MY_LOG_VERBOSE(i, …)导致i被多次递增的情况。使用do { … } while(0)包裹宏定义是一种防止语法错误的常见做法。4. 高级应用将日志与引擎其他调试工具结合UE_LOG不是孤立的它与UE5强大的调试生态系统无缝衔接。4.1 与DrawDebug系列函数联动在可视化调试方面DrawDebug函数如DrawDebugBox,DrawDebugLine,DrawDebugString是无价之宝。结合日志可以实现“看到哪里就记录到哪里”。void AMyAIController::DebugDrawPerception() { #if !UE_BUILD_SHIPPING APawn* MyPawn GetPawn(); if (!MyPawn) return; // 获取感知组件 UAIPerceptionComponent* PerceptionComp FindComponentByClassUAIPerceptionComponent(); if (PerceptionComp) { TArrayAActor* PerceivedActors; PerceptionComp-GetCurrentlyPerceivedActors(nullptr, PerceivedActors); int32 NumSeen 0; int32 NumHeard 0; for (AActor* Actor : PerceivedActors) { // 绘制连线到感知到的Actor DrawDebugLine(GetWorld(), MyPawn-GetActorLocation(), Actor-GetActorLocation(), FColor::Green, false, 0.1f, 0, 2.0f); // 同时记录日志区分视觉和听觉感知 FVector Dir Actor-GetActorLocation() - MyPawn-GetActorLocation(); float Dist Dir.Size(); Dir.Normalize(); // 这里简化判断实际应使用感知系统的刺激信息 if (Dist SightRange) { NumSeen; UE_LOG(LogAISystem, Verbose, TEXT(“Seen actor: %s at distance %.1f”), *Actor-GetName(), Dist); } else { NumHeard; UE_LOG(LogAISystem, Verbose, TEXT(“Heard actor: %s at distance %.1f”), *Actor-GetName(), Dist); } } // 在AI头顶绘制总结信息 FString DebugText FString::Printf(TEXT(“Perceived: %d (Seen:%d Heard:%d)”), PerceivedActors.Num(), NumSeen, NumHeard); DrawDebugString(GetWorld(), MyPawn-GetActorLocation() FVector(0,0,100), DebugText, nullptr, FColor::White, 0.1f, true); // 记录汇总日志 MY_LOG_VERBOSE(bAIDebugEnabled, TEXT(“Perception Debug - Total: %d, Seen: %d, Heard: %d”), PerceivedActors.Num(), NumSeen, NumHeard); } #endif }4.2 集成到Unreal Insights等性能分析工具对于性能分析UE_LOG可以与TRACE_CPUPROFILER_EVENT等宏结合在Unreal Insights时间线中标记事件并与日志关联。#include “ProfilingDebugging/CpuProfilerTrace.h” void UMyComplexSystem::Update() { // 在Insights中开始一个命名事件 TRACE_CPUPROFILER_EVENT_SCOPE(MyComplexSystem_Update); // 你的更新逻辑... Phase1(); { // 嵌套作用域用于标记子阶段 TRACE_CPUPROFILER_EVENT_SCOPE(Phase2_ExpensiveCalculation); Phase2(); // 假设这是一个昂贵的计算 // 同时记录一条日志说明此阶段耗时你可以用FPlatformTime::Cycles64()计算精确耗时 UE_LOG(LogMyGame, Verbose, TEXT(“Phase2 calculation completed.”)); } Phase3(); }在Unreal Insights中运行游戏并捕获数据你就能在时间线上清晰地看到MyComplexSystem_Update和其子阶段Phase2_ExpensiveCalculation的耗时柱状图。结合同一时间点输出的日志你可以将性能瓶颈与具体的游戏事件如“Phase2 calculation completed”对应起来分析效率低下的原因。5. 常见问题排查与性能优化备忘录即使熟练使用UE_LOG在实际项目中还是会遇到一些典型问题。这里列出一个速查表。问题现象可能原因解决方案日志没有输出1. 日志级别高于当前输出设置。2. 在打包后的非开发版本中且未使用条件编译。3. 日志类别被命令行关闭。1. 检查UE_LOG的Verbosity参数如Verbose在编辑器输出日志窗口左下角或通过-LogCmds设置更低的级别如Log。2. 使用#if !UE_BUILD_SHIPPING或UE_LOG_ACTIVE宏确保调试代码在发布版中有效。3. 检查启动命令是否有-LogCmds”LogYourCategory Off”。输出日志窗口被刷屏找不到关键信息1. 在Tick中打印了Verbose或Log级别日志且未加条件。2. 多个系统同时大量打印日志。1. 为高频日志添加开关变量或提升其级别如改为VeryVerbose。2. 使用自定义日志类别并在输出日志窗口的筛选栏中只勾选你关心的类别。利用CtrlF在窗口内搜索关键词。格式化字符串错误导致崩溃或乱码1. 格式说明符与参数类型不匹配如用%d打印float。2. 使用FString时忘记解引用*。3. 字符串中包含非法字符或未使用TEXT()宏。1. 仔细核对每个占位符的类型。对于int64使用%llduint64使用%llu。2. 记住FString需要解引用*MyString。3. 所有字符串字面量用TEXT(“”)包裹确保是宽字符。日志输出有延迟或丢失1. 日志系统缓冲区未及时刷新尤其在崩溃前。2. 输出到文件时文件写入有延迟。1. 对于崩溃前最后的日志可以尝试使用FPlatformMisc::LocalPrint或printf直接输出到控制台它们可能更及时。2. 使用FFileHelper::SaveStringToFile主动将关键日志写入文件。自定义日志宏导致编译错误1. 宏定义语法错误如缺少括号。2. 宏参数在展开时产生副作用如a被多次执行。3. 在.cpp中定义的宏在其他文件中不可见。1. 使用do { … } while(0)包裹宏体。2. 避免在宏参数中使用带有副作用的表达式。如果必须在宏内部先将参数赋值给局部变量。3. 将通用的日志宏定义在公共头文件中。屏幕消息(AddOnScreenDebugMessage)不显示1.GEngine为空可能在非常早的初始化阶段。2.Key参数为正值且重复覆盖了之前的消息。3. 游戏处于暂停或特殊状态。1. 确保在BeginPlay之后或Tick中调用并检查if(GEngine)。2. 使用-1作为Key让系统自动分配唯一ID或管理好你自己的Key值。3. 检查游戏是否处于前台且未被调试器暂停。性能优化黄金法则高频函数如Tick中禁止无条件的Log及以上级别日志即使不输出函数调用和参数构造也有开销。务必使用UE_LOG_ACTIVE检查或条件编译。警惕昂贵的参数计算UE_LOG(“Stats: %s”, *CalculateComplexStats())即使日志被抑制CalculateComplexStats()这个函数也会被执行。将其放入条件判断中。合理使用日志级别将真正需要时刻关注的信息设为Log或Warning将详细的跟踪信息设为Verbose或VeryVerbose并通过配置文件或命令行动态控制。打包前清理使用项目范围的搜索查找所有Log和Verbose级别的日志评估其必要性。对于纯调试性质的考虑用#if WITH_EDITOR包裹或删除。掌握UE_LOG本质上是在掌握一种与程序运行时对话的能力。它让你从被动地猜测代码行为转变为主动地观察和验证。从今天起试着在你的下一个C函数里加入一行有意义的日志你会发现调试的世界从此变得清晰起来。
返回列表