Receber por email
← Voltar para o catálogo

godot-profile-performance

Detecta gargalos de desempenho em projetos Godot, incluindo funções _process caras, chamadas get_node() em loops, instanciações em _process, e fornece sugestões de otimização com integração do profiler Godot.

54estrelas
Atualizado há 8 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add diegosouzapw/awesome-omni-skill

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/godot-profile-performance-diegosouzapw/svg)](https://www.skillteca.com.br/skills/godot-profile-performance-diegosouzapw)

Skills relacionadas

Ver todas de Automação →

Alerta por categoria

Receba novas skills de Automação toda segunda

1 email curto, só com as skills novas de Automação. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Godot Performance Profiler

Overview

Analyzes Godot projects to detect performance bottlenecks in GDScript code. Identifies expensive operations in frame-critical functions like _process, _physics_process, and _draw. Provides actionable optimization suggestions with before/after code examples and integrates with Godot's built-in profiler for validation.

Core principle: Frame time is precious—expensive operations belong in _ready or signal handlers, not per-frame callbacks.

When to Use

Use when:

  • Frame rate drops or inconsistent performance
  • CPU usage is unexpectedly high
  • Game stutters during gameplay
  • _process functions contain complex logic
  • Memory usage grows over time
  • Before releasing a game or major update

Don't use for:

  • Network/server performance issues (use server profiling tools)
  • GPU/shader optimization (use Godot's GPU profiler)
  • Physics simulation tuning (use Godot's physics debug tools)

Detection Patterns

Heavy _process Functions

Trigger: Functions with >15 lines of code in _process or _physics_process

Detection:

# Count lines in process functions
rg "^func _process" -A 50 --glob "*.gd" | wc -l

Thresholds:

  • Warning: >15 lines
  • Critical: >30 lines

get_node() in Loops

Pattern:

# ❌ BAD: get_node() in _process
func _process(delta):
    get_node("UI/HealthBar").value = health  # Called every frame!
    get_node("Player").position = position

Detection regex:

func _process.*\n(?:.*\n)*?\s+get_node\(

Instantiation in _process

Pattern:

# ❌ BAD: Creating objects every frame
func _process(delta):
    var bullet = Bullet.new()  # Memory churn!
    add_child(bullet)

Detection keywords:

  • .new() in _process or _physics_process
  • .instantiate() in frame callbacks
  • add_child() or remove_child() in loops

Complex Operations in _physics_process

Anti-patterns:

  • Heavy pathfinding calculations
  • Complex AI state machines
  • Large array operations
  • File I/O or network calls

Analysis Procedures

Frame Time Analysis

Steps:

  1. Enable Godot profiler (Debug > Profiler)
  2. Run game for 60 seconds of typical gameplay
  3. Identify functions with high "Time (ms)" values
  4. Sort by "Time %" to find top offenders
  5. Look for functions >0.1ms per frame

Interpretation:

  • <0.01ms: Excellent
  • 0.01-0.05ms: Good
  • 0.05-0.1ms: Acceptable
  • >0.1ms: Needs optimization

Memory Usage Detection

Indicators:

  • Continuous growth in Memory monitor
  • Frequent garbage collection spikes
  • Node count increasing over time

Detection:

# Check for object creation in process functions
rg "\.new\(\)|instantiate\(\)" -B 5 -A 2 --glob "*.gd" | \
  rg -A 10 "func _process|func _physics_process"

Draw Call Optimization

Check:

  • Godot's "Monitors" tab > "Draw Calls"
  • Each unique material = additional draw call
  • GPU skinning vs CPU skinning

Optimization targets:

  • <100 draw calls for 2D games
  • <500 draw calls for simple 3D
  • Use texture atlases to reduce material switches

Physics Performance

Red flags:

  • High collision shape complexity
  • Too many rigid bodies (>100)
  • Complex polygon collisions
  • _physics_process doing non-physics work

Optimization Suggestions

Cache Node References

Before:

extends CharacterBody2D

func _process(delta):
    get_node("UI/HealthBar").value = health
    get_node("UI/ManaBar").value = mana
    get_node("UI/LevelLabel").text = str(level)

After:

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)

Impact: Eliminates 3 node lookups per frame (~0.01ms each)

Move Initialization to _ready

Before:

func _process(delta):
    var gravity = ProjectSettings.get("physics/2d/default_gravity")
    velocity.y += gravity * delta

After:

var gravity

func _ready():
    gravity = ProjectSettings.get("physics/2d/default_gravity")

func _process(delta):
    velocity.y += gravity * delta

Object Pooling for Bullets/Particles

Before:

func shoot():
    var bullet = BulletScene.instantiate()
    bullet.position = global_position
    get_parent().add_child(bullet)

After:

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

Impact: Eliminates instantiation overhead during gameplay

Signal-Based Updates

Before:

func _process(delta):
    # Checking every frame if health changed
    if health != previous_health:
        update_health_bar()
        previous_health = health

After:

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)

Batch Array Operations

Before:

func _process(delta):
    for enemy in enemies:
        if enemy.position.distance_to(player.position) < 100:
            enemy.target_player()

After:

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()

Godot Profiler Integration

Enabling the Profiler

  1. Run game with Debug > Start with Profiler
  2. Or click the "Profiler" tab in the bottom panel while game runs
  3. Enable specific monitors:
  4. CPU Time
  5. Function Time
  6. Node Count
  7. Memory
  8. Draw Calls

Key Metrics to Monitor

Frame Time (ms):

  • Shows total time per frame
  • Target: <16.67ms for 60 FPS, <33.33ms for 30 FPS
  • Spikes indicate hitches

Function Breakdown:

  • Lists all functions sorted by time
  • Look for _process, _physics_process, _draw
  • Click function name to see callers

Memory Monitor:

  • Watch for continuous growth
  • Sudden spikes indicate allocations
  • Plateaus followed by drops = garbage collection

Profiling Workflow

1. Establish baseline (unoptimized)
   └─ Record profiler data for 60 seconds

2. Identify top 3 time consumers
   └─ Sort by "Time %" in profiler

3. Apply optimization
   └─ Use patterns above

4. Validate improvement
   └─ Profile again, compare metrics
   └─ Verify frame time reduced

5. Repeat for next bottleneck

Examples

Example 1: UI Controller Optimization

Problem:

# 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)

Profiler Output:

Function          | Time (ms) | Time %
----------------------------------------
_process          | 0.18      | 12.3%
get_node          | 0.15      | 10.2%  (x5)

Optimized:

# 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
Compartilhar X LinkedIn