今日速览

  • 性能问题分三类:每帧都慢(持续低帧率)、间歇性尖峰(偶发卡顿)、非游戏时段慢(加载场景)。它们的诊断方式完全不同。
  • 官方文档反复强调一句:始终用测量(profiling/timing)来指导优化,而不是凭感觉改代码。
  • 优化是一个循环:Profile 找瓶颈 → 优化瓶颈 → 回到 Profile,而不是一次性把所有代码都优化一遍。
  • Profiler 里要看 Self 而不是只看 Inclusive——否则你会把「调用别人的函数」误判成「自己很慢」。
  • 性能不只靠事后优化,更靠性能设计(performant design):算法和数据结构选错,后期再怎么打磨都追不回来。

核心学习内容

1. 先分清「慢」的三种形态

Godot 官方《General optimization tips》把性能问题拆成三类,这决定了你该用哪把工具:

  • 持续低帧率:某个每帧都跑的逻辑太重(比如每帧 get_node()、每帧 new 对象)。用内置 Profiler 看平均值就能抓到。
  • 间歇性尖峰(stall):平时不慢,一进技能树 / 一开背包就掉帧。用 Profiler 点进性能曲线的那一帧,锁定那一段代码。
  • 非游戏时段慢:加载关卡时的 I/O 或资源实例化。这类要靠异步加载 / 后台线程解决。

很多人一上来就”全部优化一遍”,结果改了三天,帧率没变——因为他优化的是那 97% 不重要、也不慢的代码。Donald Knuth 的名言只说了前半句,“过早优化是万恶之源”,但后半句更重要:真正重要的是设计阶段就考虑性能。

2. 用 Godot 内置 Profiler 找瓶颈

打开 Debug → Debugger 面板 → Profiler 标签,运行游戏后回到编辑器点 Start(Profiler 默认关闭,因为它本身很吃性能)。关键要看懂几个指标:

  • Frame Time:整帧耗时,含渲染。默认参照系是 16.66ms = 60FPS。
  • Idle Time:_process()、计时器、camera 的更新耗时。
  • Physics Time:_physics_process() 与物理节点的耗时。

最容易踩的坑是 Inclusive vs Self:Inclusive 把子函数耗时也算进去,所以一个只负责”调用慢函数”的外层函数,看起来也会很慢。切到 Self 才是”这个函数自己花了多少时间”。文档里的例子很典型:get_neighbors、move_subject 在 Inclusive 下都像瓶颈,切到 Self 后发现它们只是”在等别人”,真正慢的是 find_nearest_neighbor。

3. 手动计时与「侦探工作」

当热点函数内部很复杂(可能是某个 for 循环、某次数据访问),就用手动微秒计时把它切开:

var t0 = Time.get_ticks_usec()
update_enemies()
var t1 = Time.get_ticks_usec()
print("update_enemies() took %d us" % (t1 - t0))

注意两个细节:跑 1000 次以上取平均(计时器精度有限、CPU 调度随机),并且留意 CPU cache——同一函数第一次跑往往更慢,因为数据还没进缓存。所以比较两版实现时,一定要用平均、要在相同条件下测。

如果连 Profiler 都指不到具体位置,就用二分法:注释掉大约一半的每帧逻辑,看性能是否明显改善,然后对有问题的那一半继续二分,快速逼近问题区域。

4. 语言与数据设计

Godot 文档提醒:引擎内置函数无论用什么语言都一样快,差别只在你自己写的计算量。GDScript 易用为王,重计算应下沉到 C#、C++ 或 GDExtension。用 C# 要小心 GC 停顿,常见做法是对象池。现代 CPU 几乎都受限于内存带宽,所以数据导向设计(让数据局部化、线性访问)比抠语法更能提速。

🎯 DeepSeek 提示词

① 性能剖析助手:把 Profiler 数据交给它诊断

适用场景:跑完 Profiler,拿到一堆函数耗时,不知道从哪下手。

你是资深 Godot 4 性能工程师。以下是我游戏的 Profiler 数据(Frame Time 单位 ms,含 Self / Inclusive / 调用次数):
【粘贴 Profiler 导出或截图文字】
游戏目标:2D 动作游戏,目标稳定 60FPS(16.66ms/帧)。
请:
1) 按"优化收益 ÷ 改动成本"排序,指出最值得先优化的 3 个瓶颈,并说明你判断依据(优先看 Self 耗时长且调用次数高的);
2) 对每个瓶颈给出具体优化手段(算法/数据结构/缓存/对象池/移出 _process 等);
3) 明确哪些是"看着慢其实只是 Inclusive 高"的伪瓶颈,不要动;
4) 用表格输出:函数名 | 问题类型(持续/尖峰/加载) | 建议改法 | 预期收益 | 风险。
约束:不要建议重写成 C++;优先 GDScript 内可落地的改动。

② 热点函数重构:保持行为不变地提速

适用场景:已定位到一个慢函数,让它帮你改写。

你是 Godot 4 GDScript 优化专家。下面是我的一段被 Profiler 标记为热点的函数:
【粘贴函数代码】
请重构它,要求:
- 行为与输出完全不变(包括边界情况);
- 减少每帧重复计算:把循环不变量提到循环外、缓存 get_node/属性访问、避免每帧 new 对象或字符串拼接;
- 若适合,使用 Typed GDScript(: int / : Array[...])提升速度;
- 输出格式:1) 重构后的完整代码;2) 改动清单(每条说明为什么快);3) 如何用 Time.get_ticks_usec() 验证提速。
约束:不要引入新依赖;不要改动函数签名以免影响调用方。

③ 性能陷阱代码审查

适用场景:写完一批逻辑,先让 DeepSeek 扫一遍常见跑分杀手。

你是 Godot 4 代码审查员,专注性能。审查以下脚本,找出每帧路径(_process / _physics_process)中的性能陷阱:
【粘贴脚本】
重点检查:每帧 get_node()/get_parent()、未缓存的节点引用、每帧 new 对象或数组、字符串频繁拼接、不必要的 signal 连接、在 _process 里做物理/寻路、重复的 type check。
输出:表格「行号 | 问题 | 影响 | 修法(给出替换代码片段)」;最后按严重程度排序,并标出必须修的 top 3。
约束:只报真问题,不要为凑数把正常代码也列进来。

学习心得

对”用 DeepSeek 从零做一款 Steam 游戏”来说,这一课的价值在于改变工作顺序:先让 Godot 的 Profiler 说话,再把数据丢给 DeepSeek 分析,最后只改真正拖帧的那几个函数。DeepSeek 很擅长”看数据、排优先级、给改法”,但它看不见你的运行时——所以测量必须由引擎做,分析才交给 AI。这套”引擎测量 + AI 分析 + 人工验证”的闭环,能让我们在没有性能团队的情况下,也把游戏稳稳压进 60 帧,这对 Steam 上”低配也能玩”的评价和 Steam Deck 兼容性都直接加分。

明日关注

  • 在自己项目里跑一次 Profiler,记录当前 Frame / Idle / Physics 三个耗时基线。
  • 用提示词 ① 分析一次结果,挑出 top 1 瓶颈并应用提示词 ② 重构。
  • 给一个热点函数加上 Time.get_ticks_usec() 基准测试,对比优化前后平均值。

本文由自动化学习流水线每日生成,坚持即学即记。