원클릭으로
godot-profile-performance
检测 Godot 项目中的性能瓶颈,包括高开销的 _process 函数、循环中的 get_node() 调用、_process 中的实例化操作,并提供优化建议和 Godot 内置分析器集成
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
检测 Godot 项目中的性能瓶颈,包括高开销的 _process 函数、循环中的 get_node() 调用、_process 中的实例化操作,并提供优化建议和 Godot 内置分析器集成
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
当 Godot 代码中存在通过 get_node()、get_parent() 或直接引用产生的紧耦合依赖时使用。 检测耦合模式并将其转换为基于 Signal 的通信方式。组件变得独立、可测试且可复用。 在改进架构的同时精确保留原有行为。
当 Godot 代码中存在相互冲突的操作导致未定义行为时使用。检测同一属性在多处设置 (_ready、_process、代码+编辑器)、同一 Signal 多次连接、冲突的物理模式、 竞争的动画等问题。自动通过明确的所有权归属解决冲突。
用于创建带有自定义面板、停靠栏和工具的 Godot 编辑器插件。生成 plugin.cfg 配置、 EditorPlugin 脚本模板、自定义编辑器 UI 组件,并集成 ProjectSettings。按照 Godot 4.x 最佳实践创建完整的插件结构。
当 Godot 代码中存在硬编码的游戏数据(如 const 数组、字典或内嵌值)时使用。检测内联数据 如敌人属性、物品定义、关卡配置等。自动提取为 .tres Resource 文件,使数据在编辑器中 可见、易于修改,并支持数据驱动的设计。
当构建 Godot 功能时,代码使用 .new() 创建节点而非使用场景时触发。 检测 Timer.new()、Area2D.new()、Sprite2D.new() 等代码创建的对象。 自动生成 .tscn 场景文件,更新父脚本使用 @onready 引用,并创建可复用的组件库。
当 Godot 项目在编辑器(.tscn)和代码(.gd)之间存在位置冲突、相机跟随背景、 或运行时位置与编辑器预览不匹配时使用。编排所有 3 个位置同步子技能: sync-static-positions、sync-camera-positions 和 sync-parallax。 每个操作针对特定的位置冲突类型。
| name | godot-profile-performance |
| version | 1.0.0 |
| displayName | Godot 性能分析器 |
| description | 检测 Godot 项目中的性能瓶颈,包括高开销的 _process 函数、循环中的 get_node() 调用、_process 中的实例化操作,并提供优化建议和 Godot 内置分析器集成 |
| author | Asreonn |
| license | MIT |
| category | game-development |
| type | tool |
| difficulty | intermediate |
| audience | ["developers"] |
| keywords | ["godot","performance","profiler","optimization","_process","frame-time","bottleneck","memory","gdscript"] |
| platforms | ["macos","linux","windows"] |
| repository | https://github.com/asreonn/godot-superpowers |
| homepage | https://github.com/asreonn/godot-superpowers#readme |
| permissions | {"filesystem":[{"read":"**/*.gd"},{"read":"**/project.godot"},{"write":"**/*.gd"},{"write":"**/optimization_report.md"}]} |
分析 Godot 项目以检测 GDScript 代码中的性能瓶颈。识别帧关键函数(如 _process、_physics_process 和 _draw)中的高开销操作。提供可操作的优化建议,包含优化前后的代码示例,并与 Godot 内置分析器集成进行验证。
核心原则: 帧时间非常宝贵——高开销操作应放在 _ready 或信号处理函数中,而非每帧回调中。
适合使用:
_process 函数包含复杂逻辑不适合用于:
触发条件: _process 或 _physics_process 中代码行数 >15 行的函数
检测方法:
# Count lines in process functions
rg "^func _process" -A 50 --glob "*.gd" | wc -l
阈值:
模式:
# ❌ 错误:在 _process 中调用 get_node()
func _process(delta):
get_node("UI/HealthBar").value = health # Called every frame!
get_node("Player").position = position
检测正则:
func _process.*\n(?:.*\n)*?\s+get_node\(
模式:
# ❌ 错误:每帧创建对象
func _process(delta):
var bullet = Bullet.new() # Memory churn!
add_child(bullet)
检测关键词:
_process 或 _physics_process 中的 .new().instantiate()add_child() 或 remove_child()反模式:
步骤:
判读标准:
0.1ms:需要优化
指标:
检测方法:
# Check for object creation in process functions
rg "\.new\(\)|instantiate\(\)" -B 5 -A 2 --glob "*.gd" | \
rg -A 10 "func _process|func _physics_process"
检查项:
优化目标:
危险信号:
_physics_process 执行非物理相关工作优化前:
extends CharacterBody2D
func _process(delta):
get_node("UI/HealthBar").value = health
get_node("UI/ManaBar").value = mana
get_node("UI/LevelLabel").text = str(level)
优化后:
extends CharacterBody2D
@onready var health_bar = $UI/HealthBar
@onready var mana_bar = $UI/ManaBar
@onready var level_label = $UI/LevelLabel
func _process(delta):
health_bar.value = health
mana_bar.value = mana
level_label.text = str(level)
影响: 每帧消除 3 次节点查找(每次约 0.01ms)
优化前:
func _process(delta):
var gravity = ProjectSettings.get("physics/2d/default_gravity")
velocity.y += gravity * delta
优化后:
var gravity
func _ready():
gravity = ProjectSettings.get("physics/2d/default_gravity")
func _process(delta):
velocity.y += gravity * delta
优化前:
func shoot():
var bullet = BulletScene.instantiate()
bullet.position = global_position
get_parent().add_child(bullet)
优化后:
var bullet_pool: Array[Bullet] = []
func _ready():
# Pre-instantiate bullets
for i in range(50):
var bullet = BulletScene.instantiate()
bullet.hide()
bullet_pool.append(bullet)
get_parent().add_child(bullet)
func shoot():
for bullet in bullet_pool:
if not bullet.visible:
bullet.position = global_position
bullet.show()
bullet.activate()
return
影响: 消除游戏过程中的实例化开销
优化前:
func _process(delta):
# Checking every frame if health changed
if health != previous_health:
update_health_bar()
previous_health = health
优化后:
signal health_changed(new_health)
var health = 100:
set(value):
if health != value:
health = value
health_changed.emit(health)
func _ready():
health_changed.connect(update_health_bar)
优化前:
func _process(delta):
for enemy in enemies:
if enemy.position.distance_to(player.position) < 100:
enemy.target_player()
优化后:
var check_timer = 0.0
const CHECK_INTERVAL = 0.1 # Check 10x per second, not 60x
func _process(delta):
check_timer += delta
if check_timer >= CHECK_INTERVAL:
check_timer = 0
update_enemy_targets()
func update_enemy_targets():
for enemy in enemies:
if enemy.position.distance_to(player.position) < 100:
enemy.target_player()
帧时间(ms):
函数分析:
_process、_physics_process、_draw内存监控:
1. 建立基准线(未优化)
└─ 记录 60 秒的分析器数据
2. 识别前 3 大时间消耗者
└─ 在分析器中按 "Time %" 排序
3. 应用优化
└─ 使用上述模式
4. 验证改进效果
└─ 重新分析,对比指标
└─ 验证帧时间降低
5. 对下一个瓶颈重复以上步骤
问题:
# ui_controller.gd
extends Control
func _process(delta):
# Called every frame - 5 node lookups!
get_node("HealthBar").value = player.health
get_node("ManaBar").value = player.mana
get_node("LevelLabel").text = "Level: " + str(player.level)
get_node("XpBar").value = player.xp
get_node("GoldLabel").text = "Gold: " + str(player.gold)
分析器输出:
Function | Time (ms) | Time %
----------------------------------------
_process | 0.18 | 12.3%
get_node | 0.15 | 10.2% (x5)
优化后:
# ui_controller.gd
extends Control
@onready var health_bar = $HealthBar
@onready var mana_bar = $ManaBar
@onready var level_label = $LevelLabel
@onready var xp_bar = $XpBar
@onready var gold_label = $GoldLabel
func _ready():
# Connect to player signals instead of polling
player.health_changed.connect(_on_health_changed)
player.mana_changed.connect(_on_mana_changed)
player.leveled_up.connect(_on_leveled_up)
player.xp_changed.connect(_on_xp_changed)
player.gold_changed.connect(_on_gold_changed)
func _on_health_changed(value): health_bar.value = value
func _on_mana_changed(value): mana_bar.value = value
func _on_leveled_up(level): level_label.text = "Level: " + str(level)
func _on_xp_changed(value): xp_bar.value = value
func _on_gold_changed(value): gold_label.text = "Gold: " + str(value)
结果:
Function | Time (ms) | Time %
----------------------------------------
_process | 0.00 | 0.0% (removed!)
_on_health_changed| 0.01 | 0.7% (event-driven)
问题:
# spawner.gd
extends Node2D
func _process(delta):
if enemies.size() < max_enemies:
var enemy = EnemyScene.instantiate() # Memory churn!
enemy.position = random_position()
add_child(enemy)
enemies.append(enemy)
内存泄漏: 持续实例化且无对象池
优化后:
# spawner.gd
extends Node2D
var spawn_timer = 0.0
const SPAWN_RATE = 2.0 # Check spawn every 2 seconds
func _process(delta):
spawn_timer += delta
if spawn_timer >= SPAWN_RATE:
spawn_timer = 0
try_spawn()
func try_spawn():
if enemies.size() < max_enemies:
spawn_enemy()
func spawn_enemy():
# Consider object pool for frequent spawns
var enemy = EnemyScene.instantiate()
enemy.position = random_position()
add_child(enemy)
enemies.append(enemy)
进一步改进: 对频繁生成的敌人使用对象池
问题:
# player.gd
extends CharacterBody2D
func _physics_process(delta):
# Heavy calculation every physics frame
var nearby = get_tree().get_nodes_in_group("enemies")
for enemy in nearby:
if global_position.distance_to(enemy.global_position) < detection_radius:
enemy.set_target(self)
# Do physics
velocity.y += gravity * delta
move_and_slide()
分析器输出:
Function | Time (ms) | Time %
--------------------------------------------
_physics_process | 0.45 | 28.5%
get_nodes_in_group | 0.25 | 15.8%
distance_to | 0.12 | 7.6%
优化后:
# player.gd
extends CharacterBody2D
var detection_timer = 0.0
const DETECTION_RATE = 0.2 # 5x per second
func _physics_process(delta):
# Separate physics from AI
velocity.y += gravity * delta
move_and_slide()
# Run detection less frequently
detection_timer += delta
if detection_timer >= DETECTION_RATE:
detection_timer = 0
update_enemy_detection()
func update_enemy_detection():
var nearby = get_tree().get_nodes_in_group("enemies")
for enemy in nearby:
if global_position.distance_to(enemy.global_position) < detection_radius:
enemy.set_target(self)
结果: 物理帧时间降低约 60%
性能优化成功的标志:
_process 函数代码行数 <15 行_process 或 _physics_process 中零 get_node() 调用.new() 或 .instantiate() 调用_ready() 或 @onready 中缓存_physics_process 仅包含物理相关代码| 模式 | 检测方法 | 修复方案 | 影响 |
|---|---|---|---|
| _process 中的 get_node() | 搜索:_process 后的 get_node | 使用 @onready 缓存 | 每次调用约 0.01ms |
| _process 中的 .new() | 搜索:帧函数中的 .new() | 使用对象池 | 消除 GC 压力 |
| 重型 _process | 行数 >15 | 移至信号/定时器 | 减少每帧负载 |
| 物理 + AI | _physics_process 中的 AI | 使用定时器分离 | 降低 5-10 倍 |
| 未缓存的设置 | 循环中的 ProjectSettings.get() | 在 _ready 中缓存 | 一次性开销 |
问题: 技术债务累积,后期更难修复 修复: 尽早且频繁地分析,及时修复瓶颈
问题: 优化了不是瓶颈的代码 修复: 始终先分析,聚焦最大时间消耗者
问题: 花数小时节省 0.001ms 修复: 针对 >0.1ms 的函数,忽略其余
问题: 假设优化有效却未测量 修复: 始终在优化前后运行分析器
问题: 调试版本有不同的性能特征 修复: 对发布版本进行分析以获取准确数据