Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
zate avatar

Godot Optimization

  • 612 installs
  • 17 repo stars
  • Updated November 9, 2025
  • zate/cc-godot

godot-optimization is a game development skill that detects and fixes performance bottlenecks in Godot 2D and 3D projects using built-in profilers and optimization techniques for developers improving frame rates before r

About

godot-optimization is a Claude Code skill from zate/cc-godot that applies expert Godot performance knowledge to profiling, bottleneck identification, and optimization for both 2D and 3D games. The skill uses Godot's Debug → Profiler while the game runs, monitoring Frame, Monitors, Network, and Visual tabs with key FPS and frame-time metrics. Allowed tools include mcp__godot__* plus Read, Write, Edit, Glob, and Grep for codebase-aware fixes. Developers reach for godot-optimization when frame rates drop, stutters appear, or shipping builds fail performance targets on target hardware. The workflow moves from reproducible profiling sessions to targeted scene, script, and rendering tweaks rather than speculative micro-optimizations. Use it during ship-phase performance passes once core gameplay is feature-complete.

  • Scans Godot scenes, scripts, and nodes for common optimization issues
  • Generates prioritized fix lists with GDScript and scene recommendations
  • Focuses on draw calls, physics, memory, and rendering performance
  • Works with both Godot 3 and Godot 4 projects
  • Outputs concrete, actionable changes you can apply immediately

Godot Optimization by the numbers

  • 612 all-time installs (skills.sh)
  • +21 installs in the week ending Jul 28, 2026 (Skillselion tracking)
  • Ranked #38 of 247 Game Development skills by installs in the Skillselion catalog
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zate/cc-godot --skill godot-optimization

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs612
repo stars17
Last updatedNovember 9, 2025
Repositoryzate/cc-godot

How do you optimize Godot game performance?

Automatically detect and fix performance bottlenecks in Godot game projects.

Who is it for?

Godot engineers profiling 2D or 3D games with stutters or low FPS before release builds.

Skip if: Non-Godot engines or greenfield projects still choosing a game framework without performance symptoms yet.

When should I use this skill?

A developer reports Godot FPS drops, asks to profile frame time, or needs bottleneck fixes in 2D or 3D scenes.

What you get

Profiler reports, identified bottlenecks, and applied Godot scene or script optimizations improving FPS and frame time.

  • Profiler analysis notes
  • Optimized scenes and scripts

By the numbers

  • Godot Profiler includes 4 tabs: Frame, Monitors, Network, and Visual
  • Covers optimization for both 2D and 3D Godot game projects

Files

SKILL.mdMarkdownGitHub ↗

You are a Godot performance optimization expert with deep knowledge of profiling, bottleneck identification, and optimization techniques for both 2D and 3D games.

Performance Profiling

Built-in Godot Profiler

Accessing the Profiler:

  • Debug → Profiler (while game is running)
  • Tabs: Frame, Monitors, Network, Visual

Key Metrics to Watch:

  • FPS (Frames Per Second): Should be 60 for smooth gameplay (or 30 for mobile)
  • Frame Time: Should be <16.67ms for 60 FPS
  • Physics Frame Time: Physics processing time
  • Idle Time: Non-physics processing time

Performance Monitors

# Enable performance monitoring in code
func _ready():
    # Available monitors
    Performance.get_monitor(Performance.TIME_FPS)
    Performance.get_monitor(Performance.TIME_PROCESS)
    Performance.get_monitor(Performance.TIME_PHYSICS_PROCESS)
    Performance.get_monitor(Performance.MEMORY_STATIC)
    Performance.get_monitor(Performance.MEMORY_DYNAMIC)
    Performance.get_monitor(Performance.OBJECT_COUNT)
    Performance.get_monitor(Performance.OBJECT_NODE_COUNT)
    Performance.get_monitor(Performance.RENDER_OBJECTS_IN_FRAME)
    Performance.get_monitor(Performance.RENDER_VERTICES_IN_FRAME)

# Display FPS counter
func _process(_delta):
    var fps = Performance.get_monitor(Performance.TIME_FPS)
    $FPSLabel.text = "FPS: %d" % fps

Common Performance Bottlenecks

1. Too Many _process() Calls

Problem:

# BAD: Running every frame when not needed
func _process(delta):
    check_for_enemies()  # Expensive operation
    update_ui()
    scan_environment()

Solution:

# GOOD: Use timers or reduce frequency
var check_timer: float = 0.0
const CHECK_INTERVAL: float = 0.5  # Check twice per second

func _process(delta):
    check_timer += delta
    if check_timer >= CHECK_INTERVAL:
        check_timer = 0.0
        check_for_enemies()

# Or disable processing when not needed
func _ready():
    set_process(false)  # Enable only when active

2. Inefficient Node Lookups

Problem:

# BAD: Getting nodes every frame
func _process(delta):
    var player = get_node("/root/Main/Player")  # Slow lookup every frame
    look_at(player.global_position)

Solution:

# GOOD: Cache node references
@onready var player: Node2D = get_node("/root/Main/Player")

func _process(delta):
    if player:
        look_at(player.global_position)

3. Excessive get_tree() Calls

Problem:

# BAD: Repeated tree searches
func update():
    for enemy in get_tree().get_nodes_in_group("enemies"):
        # Process enemy

func check():
    for item in get_tree().get_nodes_in_group("items"):
        # Process item

Solution:

# GOOD: Cache groups or use signals
var enemies: Array = []

func _ready():
    enemies = get_tree().get_nodes_in_group("enemies")
    # Update when enemies added/removed via signals

4. Inefficient Collision Checking

Problem:

# BAD: Checking all objects every frame
func _physics_process(delta):
    for object in all_objects:
        if global_position.distance_to(object.global_position) < 100:
            # Do something

Solution:

# GOOD: Use Area2D/Area3D for automatic detection
@onready var detection_area = $DetectionArea

func _ready():
    detection_area.body_entered.connect(_on_body_detected)

func _on_body_detected(body):
    # Only called when something enters range
    pass

5. Too Many Draw Calls

Problem:

  • Too many individual sprites
  • No texture atlasing
  • Excessive particles
  • Too many lights

Solution:

# Use TileMap instead of individual Sprite2D nodes
# Use MultiMeshInstance for repeated objects
# Use texture atlases to batch sprites
# Limit number of lights and particles

# Example: MultiMesh for coins
@onready var multimesh_instance = $MultiMeshInstance2D

func _ready():
    var multimesh = MultiMesh.new()
    multimesh.mesh = preload("res://meshes/coin.tres")
    multimesh.instance_count = 100

    for i in range(100):
        var transform = Transform2D()
        transform.origin = Vector2(i * 50, 0)
        multimesh.set_instance_transform_2d(i, transform)

    multimesh_instance.multimesh = multimesh

6. Unoptimized Scripts

Problem:

# BAD: Creating new objects every frame
func _process(delta):
    var direction = Vector2.ZERO  # New object every frame
    direction = (target.position - position).normalized()

Solution:

# GOOD: Reuse objects
var direction: Vector2 = Vector2.ZERO  # Reused

func _process(delta):
    direction = (target.position - position).normalized()

Optimization Techniques

1. Object Pooling

# Instead of creating/destroying objects frequently
class_name ObjectPool

var pool: Array = []
var prefab: PackedScene
var pool_size: int = 20

func _init(scene: PackedScene, size: int):
    prefab = scene
    pool_size = size
    _fill_pool()

func _fill_pool():
    for i in range(pool_size):
        var obj = prefab.instantiate()
        obj.set_process(false)
        obj.visible = false
        pool.append(obj)

func get_object():
    if pool.is_empty():
        return prefab.instantiate()
    var obj = pool.pop_back()
    obj.set_process(true)
    obj.visible = true
    return obj

func return_object(obj):
    obj.set_process(false)
    obj.visible = false
    pool.append(obj)

2. Level of Detail (LOD)

# Switch to simpler models/sprites when far away
@export var lod_distances: Array[float] = [50.0, 100.0, 200.0]
@onready var camera = get_viewport().get_camera_3d()

func _process(_delta):
    var distance = global_position.distance_to(camera.global_position)

    if distance < lod_distances[0]:
        _set_lod(0)  # High detail
    elif distance < lod_distances[1]:
        _set_lod(1)  # Medium detail
    elif distance < lod_distances[2]:
        _set_lod(2)  # Low detail
    else:
        _set_lod(3)  # Minimal/hidden

func _set_lod(level: int):
    match level:
        0:
            $HighDetailMesh.visible = true
            $MedDetailMesh.visible = false
            set_physics_process(true)
        1:
            $HighDetailMesh.visible = false
            $MedDetailMesh.visible = true
            set_physics_process(true)
        2:
            $MedDetailMesh.visible = true
            set_physics_process(false)
        3:
            visible = false
            set_process(false)

3. Spatial Partitioning

# Only process objects in active area
class_name ChunkManager

var active_chunks: Dictionary = {}
var chunk_size: float = 100.0

func get_chunk_key(pos: Vector2) -> Vector2i:
    return Vector2i(
        int(pos.x / chunk_size),
        int(pos.y / chunk_size)
    )

func update_active_chunks(player_position: Vector2):
    var player_chunk = get_chunk_key(player_position)

    # Activate nearby chunks
    for x in range(-1, 2):
        for y in range(-1, 2):
            var chunk_key = player_chunk + Vector2i(x, y)
            if chunk_key not in active_chunks:
                _load_chunk(chunk_key)

    # Deactivate far chunks
    for chunk_key in active_chunks.keys():
        if chunk_key.distance_to(player_chunk) > 2:
            _unload_chunk(chunk_key)

func _load_chunk(key: Vector2i):
    # Load and activate objects in this chunk
    active_chunks[key] = true

func _unload_chunk(key: Vector2i):
    # Deactivate or remove objects in this chunk
    active_chunks.erase(key)

4. Efficient Collision Layers

# Set up collision layers properly
# Project Settings → Layer Names → 2D Physics

# Layer 1: Players
# Layer 2: Enemies
# Layer 3: Environment
# Layer 4: Projectiles

# Player only collides with enemies and environment
func _ready():
    collision_layer = 1  # Player is on layer 1
    collision_mask = 6   # Collides with layers 2 (enemies) and 3 (environment)
    # Binary: 110 = 6 (layers 2 and 3)

5. Deferred Calls for Physics

# Don't modify physics objects during physics callback
func _on_body_entered(body):
    # BAD
    # body.queue_free()
    # $CollisionShape2D.disabled = true

    # GOOD
    body.call_deferred("queue_free")
    $CollisionShape2D.call_deferred("set_disabled", true)

Memory Optimization

1. Texture Compression

Project Settings:

  • Import tab: Compress textures
  • Use VRAM compression for desktop
  • Use ETC2/ASTC for mobile
  • Reduce texture sizes where possible

2. Audio Optimization

# Use streaming for long audio (music, voice)
# Use samples for short audio (SFX)

# In import settings:
# - Loop Mode: Disabled for SFX, Forward for music
# - Compress Mode: RAM for SFX, Streaming for music

3. Scene Instancing

# Use instancing instead of duplicating
const ENEMY_SCENE = preload("res://enemies/enemy.tscn")

func spawn_enemy():
    var enemy = ENEMY_SCENE.instantiate()  # Shares resources
    add_child(enemy)

# Avoid:
# var enemy = $EnemyTemplate.duplicate()  # Duplicates everything

4. Resource Management

# Free resources when done
func remove_level():
    for child in get_children():
        child.queue_free()  # Properly free memory

    # Clear cached resources if needed
    ResourceLoader.clear_cache()

Rendering Optimization

2D Optimization

# 1. Use CanvasLayer for UI (prevents redraw of game world)
# 2. Limit particle count
# 3. Use Light2D sparingly
# 4. Batch sprites with same texture

# Efficient particle system
@onready var particles = $GPUParticles2D

func _ready():
    particles.amount = 50  # Not 500
    particles.lifetime = 1.0  # Short lifetime
    particles.one_shot = true  # Don't loop unnecessarily

3D Optimization

# 1. Use occlusion culling
# 2. Bake lighting where possible
# 3. Use LOD for distant objects
# 4. Limit shadow-casting lights

# Efficient 3D setup
func _ready():
    # Bake lighting
    $WorldEnvironment.environment.background_mode = Environment.BG_SKY

    # Limit view distance
    var camera = $Camera3D
    camera.far = 500.0  # Don't render beyond this

    # Use SDFGI for global illumination (Godot 4)
    $WorldEnvironment.environment.sdfgi_enabled = true

Profiling Workflow

1. Identify Bottleneck

1. Run game with profiler open 2. Identify which area is slowest:

  • Process
  • Physics
  • Rendering
  • Script

2. Locate Specific Issue

# Add timing to suspect code
var start_time = Time.get_ticks_usec()

# Suspect code here
_expensive_function()

var end_time = Time.get_ticks_usec()
print("Function took: ", (end_time - start_time) / 1000.0, " ms")

3. Apply Optimizations

  • Cache lookups
  • Reduce frequency
  • Use more efficient algorithms
  • Remove unnecessary work

4. Measure Results

  • Re-run profiler
  • Verify improvement
  • Ensure no regressions

Platform-Specific Optimization

Mobile Optimization

# Detect mobile platform
func _ready():
    if OS.get_name() in ["Android", "iOS"]:
        _apply_mobile_optimizations()

func _apply_mobile_optimizations():
    # Reduce particle count
    $Particles.amount = $Particles.amount / 2

    # Simplify shaders
    # Lower resolution
    get_viewport().size = get_viewport().size * 0.75

    # Disable expensive effects
    $WorldEnvironment.environment.ssao_enabled = false
    $WorldEnvironment.environment.glow_enabled = false

Web (HTML5) Optimization

# Reduce initial load
# Use streaming for assets
# Limit memory usage
# Avoid heavy physics calculations

Performance Testing Checklist

  • [ ] Frame rate stays at target (60 FPS or 30 FPS)
  • [ ] No frame drops during intense scenes
  • [ ] Memory usage stable (no leaks)
  • [ ] Load times acceptable (<3 seconds)
  • [ ] Physics stable (no jitter or tunneling)
  • [ ] Mobile: Battery usage reasonable
  • [ ] Web: Fast initial load, no freezes

When to Activate This Skill

Activate when the user:

  • Mentions lag, stuttering, or slow performance
  • Asks about optimization techniques
  • Requests performance analysis
  • Mentions FPS drops or frame rate issues
  • Asks about profiling or benchmarking
  • Needs help with mobile/web optimization
  • Mentions memory issues or crashes
  • Asks "why is my game slow?"

Optimization Workflow

1. Profile - Use Godot profiler to identify bottleneck 2. Locate - Find specific code causing issue 3. Optimize - Apply appropriate optimization technique 4. Test - Verify improvement without breaking functionality 5. Document - Note what was changed and why

Always explain:

  • WHY something is slow
  • WHAT optimization technique to use
  • HOW to implement it
  • WHAT the expected improvement is

Related skills

How it compares

Use godot-optimization for engine-native Godot profiling; generic performance skills lack Debug → Profiler tab specifics and MCP Godot integrations.

FAQ

How does godot-optimization profile games?

godot-optimization uses Godot's Debug → Profiler while the game runs. The Frame, Monitors, Network, and Visual tabs expose FPS, frame time, and subsystem metrics for 2D and 3D projects.

Which Godot project types does the skill cover?

godot-optimization addresses performance for both 2D and 3D Godot games. The skill pairs profiler analysis with script and scene optimizations using Godot MCP and filesystem editing tools.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.