mcpField
G
godot-optimization

godot-optimization

github.com/jame581开发926214 次安装2 周前更新
代码仓库

关于 godot-optimization

面向使用 GDScript 或 C# 的 Godot 4.3+ 项目的性能优化指南,介绍如何分析瓶颈,并优化渲染、物理、脚本与内存使用。

主要功能

  1. 01Profiler 与监视器使用指南
  2. 02减少绘制调用的方法
  3. 03物理性能调优模式
  4. 04GDScript 和 C# 热路径实践
  5. 05内存管理与对象池

适用场景

  1. 01定位帧耗时瓶颈
  2. 02降低渲染与物理计算负载
  3. 03管理内存分配和频繁生成的对象

安装

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

$npx skills add https://github.com/jame581/godotprompter --skill godot-optimization

安装后在 Claude Code 中输入/godot-optimization确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
cpu-bottlenecks.md
draw-calls.md
memory-management.md
physics-tuning.md

详细介绍

Godot 优化

本技能涵盖 Godot 4.3+ 项目在 GDScript 和 C# 中的性能优化,包括内置性能分析器、减少绘制调用、物理调优、GDScript 性能模式、内存管理、对象池,以及常见瓶颈参考表。

相关技能: godot-debugging 用于系统性调试和性能分析,godot-code-review 用于性能审查清单,export-pipeline 用于发布构建优化,physics-system 用于碰撞形状、层和物理体类型,2d-essentials 用于 2D 网格优化、粒子性能和绘制顺序调优,multithreading 用于将工作移出主线程,mobile-development 用于移动端性能预算。


1. 使用性能分析器

帧时间预算

在 60 fps 下,整帧(更新、物理、渲染)必须在 16.6 ms 内完成。在 30 fps 下,预算为 33.3 ms。任何单个系统若占用预算的大部分,都会成为瓶颈。

目标 FPS帧预算
1208.3 ms
6016.6 ms
3033.3 ms
阅读性能分析器输出

打开 Debugger > Profiler,点击 Start,运行要测量的场景,然后点击 Stop。

  • Frame Time — 该帧的总挂钟时间,以毫秒为单位。
  • Self — 函数内部耗费的时间,不包括 被调用函数。这是主要的热点指标。Self 时间较高的函数正在直接执行耗费大量资源的工作。
  • Total — 包括所有被调用函数的时间。可用于识别耗费资源较多的子树。
  • Calls — 每帧调用次数。每帧调用数千次的函数(即使每次调用开销很小)也可能占用大量帧时间。
  • 点击任意函数名称,可在脚本编辑器中跳转到其源代码。
# Manual micro-benchmark for a specific block
var start := Time.get_ticks_usec()
_run_expensive_operation()
var elapsed := Time.get_ticks_usec() - start
print("_run_expensive_operation: %d µs" % elapsed)

C#:

// Manual micro-benchmark using Stopwatch (high-resolution timer)
using System.Diagnostics;

var sw = Stopwatch.StartNew();
RunExpensiveOperation();
sw.Stop();
GD.Print($"RunExpensiveOperation: {sw.Elapsed.TotalMilliseconds:F3} ms");

// Alternative using Godot's built-in timer (microsecond precision)
long start = (long)Time.GetTicksUsec();
RunExpensiveOperation();
long elapsed = (long)Time.GetTicksUsec() - start;
GD.Print($"RunExpensiveOperation: {elapsed} µs");
Monitors 选项卡

游戏运行时,Debugger > Monitors 会显示实时引擎指标。点击监视器名称可打开实时图表。重点关注以下监视器:

监视器关注点
Time > FPS低于目标值 — 超出帧预算
Time > Process数值较高 — _process() 回调开销较大
Time > Physics Process数值较高 — _physics_process() 或物理模拟开销较大
Raster > Total Draw Calls高于约 500(移动端)或 2 000(桌面端)— 需要批处理
Video > Video Mem持续增长 — 纹理或网格未释放(内存泄漏)
Object > Objects场景重新加载后持续增长 — 节点未释放
Physics 3D > Active Objects简单场景中的数量很大 — 物理体未进入休眠状态
# Query any monitor at runtime from code
var fps := Performance.get_monitor(Performance.TIME_FPS)
var draw_calls := Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)
var video_ram := Performance.get_monitor(Performance.RENDER_VIDEO_MEM_USED)
print("FPS: %d | Draw calls: %d | VRAM: %.1f MB" % [fps, draw_calls, video_ram / 1_048_576.0])

C#:

// Query any monitor at runtime from code
double fps = Performance.GetMonitor(Performance.Monitor.TimeFps);
double drawCalls = Performance.GetMonitor(Performance.Monitor.RenderTotalDrawCallsInFrame);
double videoRam = Performance.GetMonitor(Performance.Monitor.RenderVideoMemUsed);
GD.Print($"FPS: {fps:F0} | Draw calls: {drawCalls:F0} | VRAM: {videoRam / 1_048_576.0:F1} MB");

2. 绘制调用优化

每个无法与相邻项批处理的独立网格、精灵或画布项都会产生一次绘制调用。减少绘制调用是最有效的优化手段之一,尤其是在移动端。只要相邻项共用纹理和材质,2D 批处理便会自动进行(Compatibility;4.4 起的 Forward+/Mobile 也支持),因此应使用图集精灵、减少唯一材质的数量,并裁剪屏幕外的工作。CanvasGroup 不会进行批处理:它会将子项渲染到后备缓冲区。

完整方案请参阅 references/draw-calls.md(2D 批处理打断规则、使用每实例 uniform 的共享材质、纹理图集、VisibleOnScreenNotifier2D/3D 裁剪,以及 3D LOD 切换)。


3. 物理优化

物理调优的关键在于尽量减少宽相位处理,并避免在移动物体上使用网格碰撞体。将碰撞掩码精简到每个物体实际需要的层;对所有会移动的物体,使用基本形状替代 ConcavePolygonShape3D;对于重叠检测,优先使用 Area2D/3D,而不是每帧执行射线检测。

完整方案请参阅 references/physics-tuning.md(层/掩码位示例、碰撞形状开销表、Engine.physics_ticks_per_second 调优,以及 Area 与射线检测的用法模式)。


4. GDScript 性能

优化 GDScript 热路径,关键在于消除每帧分配、使用 StringName 而非 String 进行比较、使用类型化数组 / PackedArray,并在类作用域预加载资源。C# 也适用相同的分配原则(用 List<T> 替代类型化 Array[T],并使用 static readonly StringName 字段)。

完整方案请参阅 references/cpu-bottlenecks.md(缓存组查询、复用向量局部变量、&"..." 字面量、PackedVector2Array、静态类型、preload 与 load 的比较,以及 C# 对应方案)。


5. 内存管理

在场景重新加载期间,留意 Performance.MEMORY_STATIC 和 OBJECT_COUNT——持续增长意味着存在引用泄漏。按路径加载的资源会被缓存并共享;需要按实例修改时,请调用 .duplicate()。对于节点,始终优先使用 queue_free();如果节点正在发出信号,在编辑器和调试导出版本中调用 free() 会被拒绝(“Object is locked and can't be freed”),但发布导出版本会跳过该检查,并在信号发出过程中释放节点(行为未定义)。

完整方案请参阅 references/memory-management.md(Performance 单例查询、ResourceLoader 缓存语义、queue_free 与 free 对照表,以及用于子弹 / 特效 / 粒子的完整 GDScript 和 C# 对象池实现)。


6. 常见瓶颈

问题诊断工具修复方法
绘制调用过多Debugger > Monitors Raster > Total Draw Calls;Viewport > Debug > Draw Calls 覆盖层让相邻的 2D 元素使用同一纹理和材质,以便批处理;3D 使用 MultiMeshInstance3D 或合并网格;使用纹理图集;减少不同材质的数量
_process 中的 GDScript 开销过大Profiler > Self 列中脚本函数排名靠前将逻辑移至 _physics_process(默认固定为每秒 60 次——高帧率下调用频率低于 _process),缓存查询结果,避免每帧分配,并考虑在紧密循环中使用 C#
信号连接过多Profiler 显示信号分发开销;手动检查 get_signal_connection_list()移除多余连接;对于高频数据,优先轮询而非每帧发送信号;对于触发后即弃用的连接,使用 CONNECT_ONE_SHOT
TileMap 未优化地图显示时绘制调用数偏高减少图层数量;每层使用单张图集纹理;使用 TileMapLayer(Godot 4.3+)替代旧版 TileMap
未压缩纹理过大Monitors Video > Video Mem 数值偏高;在 Import 停靠面板中检查纹理设置在 Import 停靠面板中启用纹理压缩(VRAM Compressed);使用 mipmap;将不会近距离查看的资源分辨率减半
活跃物理实体过多Monitors Physics 3D > Active Objects 数值偏高;Profiler 中 _physics_process 耗时较长为 RigidBody3D 启用休眠(can_sleep = true);降低物理更新频率;将远处的实体替换为虚假动画;使用图层 / 掩码缩小碰撞检查范围
热路径中的字符串操作Profiler 中字符串密集型函数的 Self 时间较长;C# 中频繁出现 GC 暂停将 String 比较替换为 StringName(&"...");避免在 _process 中格式化 String;构建字符串一次并缓存
热路径中调用 instantiate()Profiler 显示 PackedScene.instantiate 的 Self 时间较长(启用 Editor Settings > Debugger > Profile Native Calls)实现对象池(参见 references/memory-management.md);在启动时预加载场景;在加载界面生成对象,而非在游戏过程中生成

7. 最常见的反模式

  • 在 _process 中分配内存——每帧都创建新的 Array、Dictionary、String 或 Vector。缓存容器,并直接修改字段。参见 references/cpu-bottlenecks.md。
  • 在移动的物体上使用网格碰撞体——在 CharacterBody3D 或 RigidBody3D 上使用 ConcavePolygonShape3D。改用胶囊体、盒体或凸包。参见 references/physics-tuning.md。
  • 为每个实例创建唯一材质——在 _ready() 中执行 material_override = SomeMaterial.new() 会破坏批处理。共享同一个材质,并通过着色器参数进行变化。参见 references/draw-calls.md。
  • 在热点路径中调用 load()——从 _process 或 _physics_process 调用 load("res://...")。在类作用域使用 const X := preload(...)。参见 references/cpu-bottlenecks.md。
  • 对短生命周期对象使用 instantiate() + queue_free()——例如子弹、命中效果和粒子。使用对象池。参见 references/memory-management.md。

8. 检查清单

在发布前或调查性能问题时,请逐项检查。

性能分析器

  • 在负载最高的游戏场景中运行性能分析器。
  • 确认没有任何单个函数的 Self 时间超过帧预算的 30%。
  • 确认总帧时间不超过预算(60 fps 时为 16.6 ms)。

绘制调用

  • 绘制调用数量符合目标(移动端 ≤500,桌面端 ≤2 000)。
  • 相邻绘制的 2D 精灵共用一张图集纹理和同一材质,以便批处理。
  • 尽可能将纹理打包到图集中;消除重复材质。
  • 离屏节点使用 VisibleOnScreenNotifier2D/3D 暂停处理。
  • 通过导入设置或手动切换逻辑为 3D 网格启用 LOD。

物理

  • 碰撞层和遮罩尽可能精简——物体不会检查不需要的层。
  • 没有移动物体使用 ConcavePolygonShape——改用胶囊体、盒体或凸体。
  • 物理更新频率适合游戏类型(回合制或俯视角游戏使用 30 Hz 可能已足够)。
  • 使用 Area2D/3D 检测重叠,而不是每帧进行射线检测。
  • 在适用情况下,RigidBody3D 节点设置 can_sleep = true。

GDScript

  • _process 或 _physics_process 内没有分配 Array、Dictionary 或 String。
  • 所有热点路径中的字符串比较都使用 StringName(&"...")。
  • 所有热点路径中的数组都已指定类型(Array[T] 或 PackedArray)。
  • 所有热点路径中的函数参数和返回类型都已静态指定类型。
  • 所有场景和资源引用都在类作用域使用 preload,而不是每帧调用 load。

内存

  • Performance.get_monitor(Performance.MEMORY_STATIC) 在场景重新加载之间保持稳定。
  • 需要逐实例修改的资源都使用 .duplicate() 复制。
  • 除非明确需要同步拆除,否则移除节点时都使用 queue_free()。

对象池

  • 子弹、命中效果、粒子和其他频繁生成的对象都使用对象池。
  • 对象池的初始大小足够大,避免正常游戏过程中动态扩容。
  • 池化对象重新激活时会重置所有状态(位置、速度、信号)。

评论

支持 Markdown