首页 / 资讯中心 / 文章详情

UE4控制台命令全解析:从CVar、stat到性能排查实战

UE4控制台命令全解析:从CVar、stat到性能排查实战 ★ FEATURED ARTICLE
1. 控制台命令的本质一套跑在运行时的调试总线做 UE4 项目时间长了你会发现一个规律凡是能在编辑器里点出来的东西背后几乎都对应着一条控制台命令。它不是某个插件附带的小工具而是引擎在运行时给自己留的一整套调试总线负责把外部的字符串指令翻译成对引擎内部状态的读写。会用它的人和不会用的人排查同一个 Bug 的时间差可能是十倍。我把这套东西理解成三个层次最外面是执行入口在哪敲中间是命令解析与分发谁来处理最里面是实际生效的变量或函数改了什么。很多同行卡在命令敲了没反应本质上是没搞清楚这三层里哪一层断掉了。比如在 Shipping 包上敲stat fps没反应问题不在命令本身而在于控制台这一层被裁剪掉了。这篇文章我就按这三个层次往下拆顺带把大家问得最多的两个问题——外接设备映射怎么调、查询和物理模拟到底差在哪——一起讲透。先给个整体印象UE4 的控制台命令大致分两类。一类是控制台变量业内习惯叫 CVar本质是引擎里注册过的一个有名字、有类型、有默认值的全局变量r.ScreenPercentage、p.EnableAsyncScene、t.MaxFPS都属于这一类。另一类是执行命令它对应一次函数调用比如open切关卡、Slomo改时间膨胀、obj gc强制垃圾回收。这两类的调用方式完全一样但一个改状态、一个做动作理解这个区别对后面排查问题很关键。1.1 从 ShowFlags 和 CVar 聊起ShowFlag系列是很多人接触 UE4 控制台命令的起点因为它直观——敲ShowFlag.Collision 1屏幕上立刻出现绿色的碰撞体线框。但这里有个容易搞混的点ShowFlag.Collision并不等同于大家常说的Show Collision。前者是直接操作渲染层的显示标记位ShowFlags 位掩码后者是引擎提供的一个语法糖式的命令内部会转发到对应的 ShowFlag 上同时也可能会做一些额外的处理。我自己的用法是ShowFlag.前缀适合做批量、精确的控制尤其是脚本化的时候因为它的语法非常规整ShowFlag.名字 0/1写进配置文件或者启动参数里不会有歧义。而Show前缀适合手敲因为短、好记敲起来顺手。两者不是替代关系是场景不同。CVar 这边要注意的是作用域和生命周期。CVar 不是永久生效的它分几个层级代码里IConsoleManager::Get().RegisterConsoleVariable()注册时给的默认值是最底层的然后DefaultEngine.ini里的[SystemSettings]段会覆盖它再往上运行时手敲的命令又覆盖配置。所以当你发现我在 ini 里改了却没生效十有八九是别的地方又把它改回去了或者你改的那个 CVar 在运行时被游戏逻辑每帧重设。注意不要盲目相信ini 里写了一定生效。有些 CVar 带有ECVF_SetByCode之类的标志位代码里的设定优先级高于配置文件这种情况下只能从代码层面改。再说类型。CVar 支持的有 bool、int、float、字符串还有一类特殊的可执行型 CVar表现形式像变量但实际是命令助手比如stat本身就是这类。类型不对会静默失败——比如你给一个 bool 型的变量传2它可能会当true处理也可能直接拒绝行为取决于版本和标志位。我踩过一次坑给某个 float 变量写了个带空格的表达式结果整个命令被截断改成了另一个变量的值排查了半小时才发现是解析问题。1.2 三种调用入口与各自的适用场景第一个入口是运行时控制台输入框默认按反引号键 或 ~取决于键盘布局唤出。在 PIE编辑器内运行和 Development 构建里都可用最灵活可以随时改随时看。缺点是没法复现你的操作不会留下痕迹第二天想再验一遍得凭记忆。第二个入口是启动参数用-ExecCmds命令1, 命令2的形式在启动时就执行一批命令。这个特别适合做预设调试环境比如跑性能测试时固定好 FPS 上限、固定好分辨率缩放、固定好 LOD 强制等级保证每次测量的起点一致。我一般会给一个专用的性能测试快捷方式来挂这串参数避免每次手敲导致变量不一致。第三个入口是代码里调用C 用GEngine-Exec(GetWorld(), TEXT(命令))蓝图用Execute Console Command节点。这条路子适合做游戏内的调试面板或者做自动化测试——把一系列命令按顺序执行然后把stat的结果抓出来比对就能做成一套回归检测。这三个入口走的是同一条分发链路但权限不同。启动参数在最早阶段执行那时候很多东西还没初始化所以有些命令此时执行会无效或者被后续初始化覆盖掉。这点在做启动就固定画质的时候经常遇到后面第 4 章会详细说怎么处理。1.3 命令解析链路为什么有的命令在有的地方不响应引擎处理一条命令字符串的流程大致是控制台输入框把字符串交给UGameEngine或UEditorEngine的Exec逐层往下传最后落到ULocalPlayer::Exec再转发给当前PlayerController然后是它关联的 Pawn 和 HUD。这个链路意味着两件事。第一必须存在一个有效的 LocalPlayer 和 PlayerController命令才可能被处理到后半段。在关卡加载过程中、在无缝切换的过渡帧里敲命令很可能就没反应。第二UFUNCTION(Exec)标记的自定义函数只有挂在链路会遍历到的那几个类上才生效。我见过同事把 Exec 函数写在了一个普通的 Actor 上然后疑惑为什么敲了没反应——它确实不会响应因为引擎不会遍历世界里所有 Actor 去找这个函数。理解了链路排查就有了方向命令没反应先确认有没有 LocalPlayer再确认这个命令要求的上下文是不是满足最后才怀疑命令拼写。顺序反过来会浪费大量时间。2. 命令命名体系拆解前缀就是一张分类地图UE4 的命令数量在几千条量级没人能全背下来。但它的命名遵循一套相当一致的约定记住前缀就相当于记住了索引目录。这套约定不是强制的所以偶尔会有漏网之鱼用错前缀但九成以上都遵守。2.1 常见前缀速查与含义把下面这张表记住你查命令的效率会有质的变化。前缀归属领域典型命令什么时候用它r.渲染r.ScreenPercentage、r.ShadowQuality画面表现、帧率、画质分级p.物理p.EnableAsyncScene、p.VisualizeMovement物理开销、角色移动、碰撞调试a.动画a.URO.Enable、a.URO.ForceAnimRate骨骼动画开销、更新频率t.引擎/时序t.MaxFPS、t.HDR帧率上限、引擎级开关fx.特效旧版粒子系统相关老项目里还能见到新项目基本被 Niagara 取代ai.AI 逻辑AI 调试输出行为树、感知系统nav.导航导航网格相关寻路异常、导航网格重生成net.网络网络驱动配置联机同步、带宽与包处理gc.垃圾回收gc.CollectGarbageEveryFrame内存抖动、卡顿定位slate.UI 框架Slate 渲染调试UMG 性能、界面重绘stat统计面板stat unit、stat rhi性能分析的核心入口Show/ShowFlag调试可视化Show Collision、ShowFlag.Bounds看碰撞、包围盒、导航、骨架表格里最容易混淆的是stat和ShowFlag的关系。前者回答花了多少时间后者回答画了什么东西。做性能排查时这两个要配合用先用stat找到时间花在哪一阶段再用对应的ShowFlag看看这一阶段到底在渲染什么。还有一类命令没有前缀是动词型的比如open、Pause、Slomo、RestartLevel、Teleport、Fly、Ghost、Walk、Reload、Kill。这类命令的特点是依赖当前上下文比如Teleport需要一个视图目标Fly需要一个可控的 Pawn。所以它们只有在合适的场景里才工作不像 CVar 那样随处可用。2.2 怎么在不查文档的情况下找到想要的命令这是我最想分享的部分因为很多人做 UE4 好几年了还在靠搜索引擎找命令名。第一招Help。不带参数执行Help引擎会尝试打开或生成一份命令列表。带上参数比如Help stat会给出这个命令的用法说明。要注意的是这个输出的完整度在各版本间不一致有时候只有一行描述有时候什么都没有。第二招DumpConsoleCommands。这是真正的大杀器。执行后引擎会把当前注册的所有控制台命令导出到一个文本文件落在工程的Saved目录下。有了这个文件你就能用本地搜索找关键词了——比在网页上翻文档快得多而且导出的内容是你当前这个版本、当前这些插件实际可用的命令不会有版本不匹配的问题。我的习惯是每建一个新项目先跑一次DumpConsoleCommands把结果存进项目的Docs目录起个带引擎版本号的名字。后面做画质分级或者性能测试时直接在里面搜效率极高。第三招查源码里的注册点。如果某个变量你想知道它的确切含义、默认值和取值范围最可靠的办法是在引擎源码里搜RegisterConsoleVariable和变量名。CVar 的注册代码通常紧挨着使用它的逻辑注释也往往比外部文档详细。特别是那些名字含糊的变量看一眼使用位置就明白了。第四招关键字联想。知道前缀之后可以大胆猜。比如你想控制阴影距离r.Shadow试一下想调纹理流送池大小r.Streaming试一下。多数情况下引擎会有自动补全提示新版编辑器支持实在没有就靠猜加筛选。3. 高频命令分组实操清单下面这几组命令是我日常用得最多的按用途分组每条都说明它解决什么问题、什么时候用、有什么坑。3.1 诊断可视化Show、ShowFlag、ShowDebug 三兄弟这三者的定位完全不同混用会很痛苦。Show是最老的接口语法是Show 名字一次只能开关一个。常用的大致有这几个Show Collision显示碰撞体线框Show Bounds显示包围盒Show Navigation显示导航网格Show Splines显示样条还有一个特殊的Show Bounds能顺便看到 Actor 的 Tick 状态。ShowFlag是新接口语法是ShowFlag.名字 0/1名字用的是显示标记的枚举名。它的优势是可以精细控制——ShowFlag.StaticMeshes 0会把所有静态网格关掉ShowFlag.SkeletalMeshes 0关掉骨骼网格ShowFlag.Particles 0关掉粒子。做渲染排查时逐个关掉这些显示类别能快速定位到是哪一类物体在拖后腿。ShowDebug是完全不同的一类的它负责在屏幕上叠加一块文本调试面板。不带参数执行ShowDebug会列出所有可用的调试类别ShowDebug AI显示 AI 状态ShowDebug Animation显示动画状态ShowDebug Camera显示相机参数ShowDebug Input显示输入状态。关闭用ShowDebug None。提示ShowDebug面板的内容是按帧刷新的如果某个字段一直是空的通常是这个类别的 Debug 数据没有被对应的系统写入而不是显示有问题。检查一下相关系统是否在那个上下文里被激活。3.2 性能统计stat 家族与 ProfileGPU、stat startfilestat家族是性能排查的主力它把引擎内部的计时器数据实时贴在屏幕上。最核心的几条stat fps只显示帧率最轻量适合长时间挂着观察波动。stat unit显示四个数字——Frame整帧时间、Game游戏线程、Draw渲染线程、GPU显卡这是判断瓶颈在哪一端的第一条命令没有之一。如果 Game 明显高于其他去查游戏逻辑如果 Draw 高去查场景里的绘制调用如果 GPU 高去查像素开销。stat unitgraph把stat unit的数值画成曲线图做性能回归的时候比看数字直观得多。它还有stat unitmax和stat unitavg两个变体分别显示峰值和平均值做长时间采样特别有用——因为瞬时数值会骗人平均值和峰值才能反映真实体验。stat scenerendering给出场景渲染的整体开销包括各种 Pass 的时间分布和可见物体数量。stat rhi关注渲染硬件接口层能看到 DrawPrimitive 调用次数和显存相关数据。stat game拆解游戏线程的开销stat physics看物理stat anim看动画stat particles看粒子stat memory看内存stat slate看 UI。这些全开会让屏幕很挤而且统计本身也有开销测量结果会失真。我的做法是一次只开一两个相关的测完立刻Stat None全关。ProfileGPU是一条容易被忽略但非常好用的命令。执行后它会把 GPU 各阶段的耗时输出到日志里粒度比stat gpu更细。缺点是会打断当前帧有轻微卡顿但抓一次性数据时很合适。做长时间性能分析就得上stat startfile和stat stopfile。这对命令会把这段时间内的详细统计写入一个文件然后用 Session Frontend 里的 Profiler 打开能看到每个函数、每个系统的耗时排序。这是做深度优化的标准流程。要注意的是文件会比较大采样时间别拉太长一般抓个十几秒就够定位问题了。实操心得stat startfile期间不要做长时间的无操作停留否则会把大量空转数据也记进去分析时反而干扰。最好让角色在典型场景里跑一圈覆盖主要的玩法路径。3.3 渲染画质调优r. 家族里最该记住的十几条r.家族成员极多我按用途挑出最实用的。分辨率相关r.ScreenPercentage控制渲染分辨率缩放这是做画质档位的核心参数。默认 100降到 70 能明显提升帧率代价是画面模糊。注意它影响的是渲染分辨率UI 通常还是按原生分辨率绘制取决于设置。r.ResolutionQuality在不同版本里语义有细微差别用之前最好确认一下当前版本的说明。抗锯齿r.PostProcessAAQuality是主开关取值 0 到 60 是关闭1 大致对应快速近似抗锯齿2 以上是时间性抗锯齿的不同质量档。做低端机档位时把它降到 1 或者 0收益很直接。r.DefaultFeature.AntiAliasing是项目默认值的开关影响的是新建场景的默认行为。后处理特效的开关是一组r.DefaultFeature.*比如r.DefaultFeature.MotionBlur、r.DefaultFeature.Bloom、r.DefaultFeature.AutoExposure。这些关掉能省一笔开销而且关掉动态曝光后画面亮度会变得稳定可预测做美术验收时反而更省事。阴影r.ShadowQuality是质量总开关r.Shadow.DistanceScale控制阴影距离缩放直接影响阴影渲染的覆盖范围是性价比很高的一个参数r.Shadow.MaxCSMResolution控制级联阴影贴图的分辨率。移动端项目里这几个参数是必调的。LOD 相关r.ForceLOD强制所有物体用某个 LOD 等级设为 -1 恢复自动。做模型检查时常用r.ForceLOD 0强制最高精度确认模型的近景效果。r.SkeletalMeshLODBias单独控制骨骼网格的 LOD 偏移。纹理流送r.TextureStreaming 0关闭流送所有纹理全量加载用来判断卡顿是不是流送引起的。注意关掉之后显存占用会飙升测试完记得开回来。r.Streaming.PoolSize控制流送池的大小单位是 MB移动端和低配机上这个值调小是常规操作但太小会导致纹理频繁抖进抖出反而更卡。光源r.VolumetricFog 0关体积雾r.AmbientOcclusionLevels控制环境光遮蔽的层级数。注意r.变量里有很多是只在初始化时读取一次的运行时改不会立刻生效。判断方法很简单——改完看画面有没有变化。没变化的话可能需要重启关卡甚至重启进程。这类变量一般和着色器编译或渲染资源分配相关。3.4 物理与查询p. 家族以及查询和物理模拟到底差在哪这一节回答一个被问得特别多的问题UE4 里查询和物理模拟到底有什么区别控制台命令上怎么体现。先说概念。物理模拟是刚体在力、重力、约束作用下由求解器计算位置和旋转的变化过程它会真正改变物体的 Transform并且产生碰撞响应。查询Query是向物理世界提问从这个点沿这个方向射一条线会撞到什么、这个盒子和哪些物体重叠了、这个胶囊体沿这条路径移动会不会受阻——它只读不写不会推动任何物体。一个生活化的类比物理模拟像是把一堆球倒进箱子里球会互相挤压、滚动、最终静止查询像是拿一根棍子伸进箱子里探一探你能知道碰到了哪个球但球不会因为你的棍子而动。控制台层面show Collision是两者共同的观察窗口它会把物理世界的几何表示画出来。但要注意它画的是碰撞几何也就是物理世界真正在用的形状而不是你在建模软件里看到的渲染网格。很多看着明明碰到了却没触发的问题根源就是碰撞几何和渲染网格不一致——show Collision一看就明白了。p.VisualizeMovement 1是排查角色移动问题的利器它会把胶囊体的 Sweep 轨迹、地面检测射线、落脚点都画出来。角色在斜坡上抖动、卡在台阶上、莫名其妙掉下去这类问题用它基本都能定位。它背后用的就是查询——每帧若干次向下的射线检测加一次水平方向的扫掠。p.ShowInitialOverlaps用来高亮那些在生成时就和其他物体重叠的组件。这个问题会引发一堆奇怪的现象比如角色被卡住、物理物体突然弹飞因为物理引擎在初始化时就在处理一个本不该存在的穿模状态。打开这个命令出问题的物体会被醒目地标出来。p.EnableAsyncScene控制异步物理场景。这个功能把一部分物理计算放到单独的线程能减轻主线程压力但它有明确的使用限制——异步场景里的物体不会和其他物体碰撞。用它之前一定要确认目标物体的交互需求用错了会出现物体穿过地面这种诡异现象。至于引擎版本较新的 UE4物理后端逐步引入了新的实现相关参数挂在p.Chaos.前缀下。具体哪些可用、行为如何取决于你锁定的引擎小版本用之前建议先在本地验证一遍不要直接照搬网上的参数。我踩过的坑曾经为了提帧把异步物理打开结果关卡里几个需要堆叠的箱子全部穿模叠在了一起。异步场景适合那些不需要交互的纯装饰性物理物体凡是参与玩法交互的老老实实留在主场景里。3.5 输入与外接设备映射的调试命令外接设备映射是实际项目里很容易出问题的一块尤其是接手柄、方向盘、飞行摇杆这类设备的时候。症状通常是编辑器里模拟输入一切正常接上真设备后要么没反应要么轴方向反了要么数值范围完全不对。ShowDebug Input是第一步要开的。它会把当前所有输入动作的状态、轴的数值实时显示出来。设备接没接上、按键有没有映射对、轴的符号是不是反的看一眼就知道。我一般会这样排查先确认按键按下去时有没有任何数值变化有变化说明设备识别和映射链路是通的问题只在方向或范围完全没变化就往前推去看设备的识别层。关于轴的范围有个特别容易踩的坑编辑器里配置的轴映射默认会有一个死区设置和缩放系数。外接设备尤其是模拟摇杆类原始输出范围往往是 -1 到 1 之外的区间或者存在零点漂移。如果死区设得太大轻微推杆就没有响应设得太小松手后数值会一直抖动。这个只能在真设备上反复试没有通用值。设备识别的干扰也值得说一下。同一台机器上如果同时接了多个同类设备索引可能会串。这时候可以逐个拔插来确认或者用调试面板看设备索引和绑定关系是否对应。多设备场景下建议在游戏设置里提供一个明确的设备选择入口而不是依赖自动识别——用户体验会好很多。另外输入相关的调试还可以配合慢放来观察。用Slomo 0.2把时间放慢输入响应的过程会被拉长能看清一次按键触发了哪些动作、轴的数值是怎么变化的。排查组合键、长按判定这类逻辑时特别好用。用完记得Slomo 1恢复。3.6 内存与资产obj、MemReport、gc 组合拳内存问题排查有一套固定的命令组合我把它叫三板斧。第一板斧是stat memory先看整体。它给出的是各子系统的内存占用概览能快速判断大头在哪——是纹理、是网格、还是蓝图或音频。第二板斧是MemReport -full它会把详细的内存报告输出到日志。这个报告非常全面包括按类别统计的资产数量、大小还有各种内存池的状态。我第一次看会觉得信息过载但它确实是定位内存问题时信息最全的入口。常用的是MemReport -full不加参数会简单一些。第三板斧是obj系列用来做资产级别的精确定位。obj list列出所有已加载的 UObject输出的是类名和数量统计。更进一步可以用类名过滤比如只看纹理能发现哪一类资产加载得异常多。obj refs可以查某个对象的引用链用来回答这个东西为什么还没被释放——这是内存泄漏排查的关键命令。obj gc强制触发一次垃圾回收用来验证这些对象是不是只是等着被回收。关于垃圾回收还有一条gc.CollectGarbageEveryFrame 1。打开它之后每帧都会做一次 GC虽然性能会明显下降但能瞬间判断出卡顿是不是 GC 引起的。如果打开后卡顿消失说明原本的卡顿是 GC 峰值导致的如果卡顿照旧那问题在别处。这个方法简单粗暴但极其有效。提示MemReport的输出默认进日志需要把日志窗口打开或者去看Saved/Logs目录下的文件。用-log启动参数可以直接把日志窗口拉出来省去来回切窗口的麻烦。4. 把命令固化进工程ini、代码、打包三件事命令敲得再熟也只能解决你自己的问题。要让团队里所有人都能用或者让某个设置在所有机器上保持一致就得把它固化下来。4.1 DefaultEngine.ini 里固化 [SystemSettings]最常用的方式是在工程的DefaultEngine.ini里加一段[SystemSettings]把要固定的 CVar 写进去。引擎启动时会读取这个段并应用里面的设定。[SystemSettings] r.ScreenPercentage100 r.Shadow.DistanceScale0.8 r.Streaming.PoolSize1500 t.MaxFPS60 p.EnableAsyncScene0这段配置的好处是所有开发者和所有构建都会带上不会出现我这台机器上帧率正常、你那台就掉帧的情况。做画质档位时我会准备几份不同的配置片段打包脚本根据目标平台选择性地拼接。但要注意优先级问题。前面说过CVar 有三层来源[SystemSettings]属于中间层。如果某个变量在代码里被以更高优先级设置ini 里的值会被覆盖。判断方法是启动后手动敲一遍同名命令看数值有没有变化。如果变了说明之前的设定没生效得从代码层面找原因。还有一个常见的疑惑是[SystemSettings]和[ConsoleVariables]的区别。前者是引擎级的系统设定段覆盖面更广用法也更标准后者在部分场景下也有效但语义不太统一。我的建议是统一用[SystemSettings]除非你明确知道某个变量必须走另一个段。4.2 C 和蓝图里执行命令做游戏内调试面板是很多项目的刚需尤其是在做主机或者不方便开键盘的平台。C 里执行命令的核心是GEngine-Execif (GEngine GetWorld()) { GEngine-Exec(GetWorld(), TEXT(stat unit)); }这里有个细节值得说第一个参数传 World 是有讲究的有些命令需要世界上下文才能找到目标对象。传nullptr在部分命令上能工作但另一些会静默失败。养成传 World 的习惯能省掉很多迷惑行为。蓝图里对应的是Execute Console Command节点。它需要一个 World Context 来确认命令执行的上下文所以通常挂在 PlayerController 或者有世界引用的蓝图里。这两个路径最终走的是同一套分发逻辑行为一致。做调试面板时我建议把命令做成配置驱动的而不是硬编码。用一张 DataTable 存按钮标题 → 命令字符串面板按表生成按钮。这样调整命令不用改代码重新加载配置就行策划和测试也能自己加。这个做法在多平台项目里特别省事。另外要提醒的是命令的返回值不要指望。多数命令返回的是布尔值表示是否被处理具体执行结果要从屏幕显示或者日志里看。所以做自动化测试时通常是执行命令后等几帧然后去读日志或者截图比对而不是读返回值。4.3 自定义 Exec 命令与 Shipping 构建的限制当内置命令不够用时可以自己注册。C 里给成员函数加UFUNCTION(Exec)标记再把这个类放在引擎会自动遍历的那条链路上PlayerController、Pawn、HUD、GameMode、GameInstance 这类函数名就会变成一条控制台命令。UFUNCTION(Exec) void SetQualityPreset(int32 Level);这样在控制台里敲SetQualityPreset 2就会调用到你的函数。这个能力在做工具链时非常好用——你可以把一堆调试开关打包成一条语义清晰的命令团队里谁都能用不需要知道背后的 CVar 名字。关于 Cheat 类命令它依赖于当前的作弊开关状态。在编辑器内运行PIE时默认是允许的但在正式构建里通常被关掉。所以凡是给策划和测试用的调试命令建议不要做成 Cheat 类而是在 Development 构建里直接可用。最后说 Shipping 构建。Shipping 是发行用的最高优化等级为了包体和安全性引擎会裁剪掉大量调试能力包括控制台输入框和一部分命令。所以你会遇到Development 包里能用的命令Shipping 包里敲不出来的情况。这是设计如此不是 Bug。那 Shipping 下怎么做调试我的做法有三条路一是保留一个 Development 构建专门给测试团队用来抓问题二是在 Shipping 里预留一套轻量的自定义调试入口通过预设的按键组合触发有限的功能而不是暴露完整控制台三是把常用的诊断信息做成常驻的性能监控 UI只在特定条件下显示。第三条路对线上问题定位帮助最大因为用户遇到问题时你能直接让他们截个图。5. 一次完整的排查实操从 45 帧到 60 帧讲理论不如讲一次真实的过程。我挑一个印象比较深的案例整个排查就是靠控制台命令完成的。5.1 现象与第一轮定位项目是一个中等规模的开放区域场景在目标配置的机器上跑出 45 帧左右的平均值目标是稳定 60。场景里没有明显的巨型模型或者超高清贴图美术资源看起来是合理的。第一步stat unit。结果是 Frame 约 22msGame 约 8msDraw 约 20msGPU 约 21ms。这里就能读出信息了Game 线程不是瓶颈Draw 和 GPU 都接近 20ms说明渲染侧压力大。同时 Draw 高而 GPU 略高于 Draw说明既有提交开销也有像素开销两头都有。第二步stat scenerendering。重点看两个数可见物体数量和各个渲染 Pass 的耗时分布。这里发现可见静态网格数量远超预期而且阴影相关的 Pass 占了相当大一块。第三步ShowFlag.Shadow 0把阴影全关再看stat unit。GPU 时间从 21ms 掉到 13ms 左右。确认阴影是 GPU 侧的大头。5.2 收窄到渲染线程的过程接下来要解释 Draw 为什么高。Draw 高通常意味着在 CPU 侧提交了太多绘制调用。先用stat rhi看 DrawPrimitive 的调用次数发现明显偏高。然后用ShowFlag.StaticMeshes 0关掉所有静态网格Draw 时间立刻降到 6ms 左右确认是静态网格的提交量问题。进一步定位用ShowFlag.Bounds 1和Show Collision辅助观察发现大量小物件被渲染而且很多是在远处根本看不清的细节。同时ShowFlag.Navigation 1时也发现场景里存在一些不该渲染的辅助几何体。这里有个关键的判断不是单个模型太重而是数量太多导致提交开销累积。这类问题用合并网格、做 LOD、调整剔除距离都能解决但优先级要按收益排。5.3 结论与验证最终的调整方向有三条一是优化阴影配置把r.Shadow.DistanceScale从默认值降到 0.8同时降低级联阴影的分辨率并关掉不需要投影阴影的物体二是对场景里的静态网格做合并减少绘制调用三是给远处的细节物件配置更激进的 LOD 和剔除距离。改完后用t.MaxFPS 60锁定上限再跑一遍完整路径stat unit稳定在 Frame 约 16msGame、Draw、GPU 三项均衡。为了确认没有引入回归我用stat unitgraph挂了十分钟曲线平稳没有周期性的尖峰——说明也没有引入 GC 或者流送引起的抖动。最后把这次的参数调整固化进DefaultEngine.ini的[SystemSettings]并把整个排查过程用到的命令整理成一张清单放进项目文档。下次遇到同类问题新人照着清单走一遍就行不用重新摸索。这个案例里真正花时间的不是敲命令而是判断哪个数值代表什么问题。命令只是把数据摆出来解读数据的能力才是核心。我的建议是每次排查完都记录下来慢慢会形成自己的判断直觉。6. 常见问题速查与避坑清单最后把平时被问得最多的问题整理成一张表遇到问题按顺序排查基本能覆盖九成情况。6.1 命令敲了没反应的排查顺序现象最可能的原因验证方式控制台完全打不开Shipping 构建裁剪了控制台换 Development 构建试一次命令输入后无任何输出命令不存在或拼写错误用Help 命令名确认数值型的命令改完没变化变量被更高优先级覆盖或需重启才生效重启关卡后再看自定义 Exec 命令不响应函数所在类不在执行链路上或未标记 Exec换到 PlayerController 上试ShowFlag 类命令无效名字拼错大小写敏感或该版本无此标记用ShowDebug列出可用项启动参数里的命令没生效执行时机太早被后续初始化覆盖改成进游戏后再手动执行验证这张表里最值得展开的是第三条。CVar 不生效的原因五花八门我遇到过的情况包括变量被游戏逻辑每帧重设、变量在 ini 里写了两遍后者覆盖前者、变量本身的优先级标志位高于配置文件。排查方法是一致的——先确认命令本身有效手动执行看有没有变化再确认有没有别的地方在改它在代码里搜变量名最后确认生效时机。另外ShowFlag的名字是大小写敏感的而且不同引擎版本的可选标记不完全一样。写配置文件时如果照抄了别的版本的写法可能会静默失效。稳妥做法是在当前版本里先用ShowDebug或者帮助命令确认一遍。6.2 几个容易踩的坑坑一stat全开导致数据失真。所有统计面板都开着的时候统计自身的开销可能占到几毫秒。做对比测试时务必保持只开必要项而且两次测试开的项目要一致否则数据没法比。坑二在加载过程中敲命令。无缝切换、关卡流送、资产异步加载这些阶段LocalPlayer 和执行上下文可能不完整命令会静默失败。养成习惯——等画面稳定了再敲。坑三把调整后的 CVar 忘了写进配置。手敲的变量只对当前会话有效。我见过团队里每个人都在自己机器上手动调结果打包出来的表现完全不同。凡是确定要保留的调整立刻写进DefaultEngine.ini。坑四用运行时命令去验证只在初始化时读取的变量。前面提过一部分渲染相关的变量只在启动时读取。你运行时改了看不到变化不代表这个变量没用可能只是生效时机不对。这种情况要配合启动参数或者 ini 来验证。坑五忽略命令对性能的干扰。像r.TextureStreaming 0、gc.CollectGarbageEveryFrame 1这类命令用它们做判断是可以的但不能用来测性能数值。切换完记得恢复默认值一个干净的基线比什么都重要。坑六跨版本照搬命令。UE4 从 4.20 到 4.27 之间命令的增删和语义变化相当多。看别人的排查记录时先确认对方用的引擎版本和你是否接近。差距大的话先验证一遍再用。6.3 建立自己的命令清单说了这么多最后还是落到一个习惯上建一份自己的命令清单。我的做法是按用途分几个文件——性能排查、画质调优、内存分析、输入调试每个文件里只放实际用过的命令附一行说明和一次真实的使用场景。这份清单的价值在于它是你验证过的不是从网上抄的。用过的命令你知道它的行为、知道它的坑、知道什么版本能用。新项目启动时把清单过一遍往往能提前避免很多重复劳动。另外清单最好带一个注意事项栏记录那些非直观的行为。比如这条命令会打断当前帧、这条改了之后必须重启、这条只在 PIE 下有效。这些信息文档里通常不写但实际用起来非常关键。我个人在实际操作中的体会是控制台命令的价值不在于你会背多少条而在于你能不能在出问题的时候快速想到该用哪条命令去把数据取出来。取数据的能力决定了排查效率而这份清单就是把这个能力沉淀下来的方式。
阅读完成 · 觉得有帮助?
咨询建站