Use when optimizing Godot games — profiler, draw calls, physics tuning, memory management, and common bottlenecks
Scanned 6/3/2026
Install to Claude Code
npx -y skills add jame581/GodotPrompter --skill godot-optimization --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Godot Optimization?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jame581-godot-optimization)More formats (shields.io, HTML) on the badges page.
---
name: godot-optimization
description: Use when optimizing Godot games — profiler, draw calls, physics tuning, memory management, and common bottlenecks
---
# Godot Optimization
This skill covers performance optimization for Godot 4.3+ projects in both GDScript and C#. It covers the built-in profiler, draw call reduction, physics tuning, GDScript performance patterns, memory management, object pooling, and a reference table of common bottlenecks.
> **Related skills:** **godot-debugging** for systematic debugging and profiling, **godot-code-review** for performance review checklist, **export-pipeline** for release build optimization, **physics-system** for collision shapes, layers, and physics body types, **2d-essentials** for 2D mesh optimization, particle performance, and draw order tuning, **multithreading** for moving work off the main thread, **mobile-development** for mobile performance budgets.
---
## 1. Using the Profiler
### Frame Time Budget
At 60 fps, the entire frame (update, physics, rendering) must complete in **16.6 ms**. At 30 fps the budget is 33.3 ms. Any single system that consumes the majority of that budget is a bottleneck.
| Target FPS | Frame budget |
|---|---|
| 120 | 8.3 ms |
| 60 | 16.6 ms |
| 30 | 33.3 ms |
### Reading Profiler Output
Open **Debugger > Profiler**, click **Start**, play through the scenario you want to measure, then click **Stop**.
- **Frame Time** — total wall-clock time for that frame in milliseconds.
- **Self** — time spent inside that function *excluding* callees. This is the primary hotspot indicator. A function with a high Self time is doing expensive work directly.
- **Total** — time including all callees. Useful for identifying expensive subtrees.
- **Calls** — call count per frame. A function called thousands of times per frame (even if each call is cheap) can dominate the frame.
- Click any function name to jump to its source in the script editor.
```gdscript
# 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#:**
```csharp
// 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 Tab
**Debugger > Monitors** shows real-time engine metrics while the game is running. Click a monitor name to open a live graph. Key monitors to watch:
| Monitor | What to watch for |
|---|---|
| `Time > FPS` | Below target — frame budget overrun |
| `Time > Process` | High — `_process()` callbacks are expensive |
| `Time > Physics Process` | High — `_physics_process()` or physics sim is expensive |
| `Render > Total Draw Calls` | Above ~500 (mobile) or ~2 000 (desktop) — needs batching |
| `Render > Video RAM` | Steadily growing — unfreed textures or meshes (memory leak) |
| `Object > Object Count` | Growing across scene reloads — nodes are not being freed |
| `Physics 3D > Active Bodies` | Large count in simple scenes — bodies not sleeping |
```gdscript
# 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#:**
```csharp
// 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. Draw Call Optimization
Every distinct mesh, sprite, or canvas item that cannot be batched with its neighbours costs one draw call. Reducing draw calls is one of the highest-leverage optimisations, especially on mobile — wrap 2D groups sharing a texture in `CanvasGroup`, keep unique-material count low, atlas sprites, and cull off-screen work.
> See [references/draw-calls.md](references/draw-calls.md) for the full recipes (CanvasGroup batching constraints, shared shader-parameter materials, texture atlases, `VisibleOnScreenNotifier2D/3D` culling, and 3D LOD swapping).
---
## 3. Physics Optimization
Physics tuning hinges on minimising broadphase work and avoiding mesh colliders on moving bodies. Trim collision masks to only the layers each body actually needs, replace `ConcavePolygonShape3D` with primitives on anything that moves, and prefer `Area2D/3D` over per-frame raycasts for overlap detection.
> See [references/physics-tuning.md](references/physics-tuning.md) for the full recipes (layer/mask bit examples, collision-shape cost table, `Engine.physics_ticks_per_second` tuning, Area-vs-raycast patterns).
---
## 4. GDScript Performance
Hot-path GDScript wins come from eliminating per-frame allocations, comparing `StringName` instead of `String`, using typed arrays / `PackedArray`s, and `preload`-ing resources at class scope. The same allocation discipline applies to C# (with `List<T>` in place of typed `Array[T]` and `static readonly StringName` fields).
> See [references/cpu-bottlenecks.md](references/cpu-bottlenecks.md) for the full recipes (cached group queries, reused vector locals, `&"..."` literals, `PackedVector2Array`, static typing, `preload` vs `load`, plus C# parity blocks).
---
## 5. Memory Management
Watch `Performance.MEMORY_STATIC` and `OBJECT_COUNT` across scene reloads — steady growth means leaked references. Resources loaded by path are cached and shared; call `.duplicate()` when you need per-instance mutation. Always prefer `queue_free()` for nodes; `free()` inside a self-emitted signal will crash.
> See [references/memory-management.md](references/memory-management.md) for the full recipes (Performance singleton queries, ResourceLoader cache semantics, `queue_free` vs `free` table, plus the full GDScript and C# **object pool** implementations for bullets/effects/particles).
---
## 6. Common Bottlenecks
| Problem | Diagnosis tool | Fix |
|---|---|---|
| Too many draw calls | Debugger > Monitors `Render > Total Draw Calls`; Viewport > Debug > Draw Calls overlay | Use `CanvasGroup` for 2D batching; merge meshes for 3D; use texture atlases; reduce unique materials |
| Heavy GDScript in `_process` | Profiler > Self column shows script functions at top | Move logic to `_physics_process` (runs less often), cache queries, avoid per-frame allocations, consider C# for tight loops |
| Excessive signal connections | Profiler shows signal dispatch overhead; manually audit `get_signal_connection_list()` | Remove redundant connections; prefer polling over per-frame signals for high-frequency data; use `CONNECT_ONE_SHOT` for fire-and-forget |
| Unoptimised TileMap | Profiler shows `TileMap._process` or high draw call count | Split into fewer layers; use a single atlas texture per layer; disable `use_parent_material` if not needed; use `TileMapLayer` (Godot 4.3+) instead of legacy TileMap |
| Large uncompressed textures | Monitors `Render > Video RAM` is high; check Import dock for texture settings | Enable texture compression (VRAM Compressed) in the Import dock; use mipmaps; halve resolution of assets not viewed up-close |
| Too many active physics bodies | Monitors `Physics 3D > Active Bodies` is high; slow `_physics_process` in Profiler | Enable sleeping on `RigidBody3D` (`can_sleep = true`); lower physics tick rate; replace distant bodies with fake animations; use layers/masks to narrow collision checks |
| String operations in hot paths | Profiler shows `String` allocation functions; high GC pressure | Replace `String` comparisons with `StringName` (`&"..."`); avoid `String` formatting in `_process`; build strings once and cache |
| `instantiate()` in hot paths | Profiler shows `PackedScene.instantiate` with high Self time | Implement object pooling (see `references/memory-management.md`); preload scenes at startup; spawn during loading screens rather than during gameplay |
---
## 7. Top Anti-Patterns
- **Allocating in `_process`** — new Arrays, Dictionaries, Strings, or Vector constructors per frame. Cache the container, mutate fields in place. See [references/cpu-bottlenecks.md](references/cpu-bottlenecks.md).
- **Mesh colliders on moving bodies** — `ConcavePolygonShape3D` on a `CharacterBody3D` or `RigidBody3D`. Use a capsule, box, or convex hull instead. See [references/physics-tuning.md](references/physics-tuning.md).
- **Unique materials per instance** — `material_override = SomeMaterial.new()` in `_ready()` breaks batching. Share one material; vary via shader parameters. See [references/draw-calls.md](references/draw-calls.md).
- **`load()` in hot paths** — calling `load("res://...")` from `_process` or `_physics_process`. Use `const X := preload(...)` at class scope. See [references/cpu-bottlenecks.md](references/cpu-bottlenecks.md).
- **`instantiate()` + `queue_free()` for short-lived objects** — bullets, hit effects, particles. Pool them. See [references/memory-management.md](references/memory-management.md).
---
## 8. Checklist
Work through this list before shipping or when investigating a performance complaint.
**Profiler**
- [ ] Run the Profiler during the most demanding gameplay scenario.
- [ ] Confirm no single function's Self time exceeds 30% of the frame budget.
- [ ] Confirm total frame time stays under budget (16.6 ms at 60 fps).
**Draw Calls**
- [ ] Draw call count is within target (≤500 mobile, ≤2 000 desktop).
- [ ] 2D sprite groups that share a texture are wrapped in `CanvasGroup`.
- [ ] Textures are atlas-packed where possible; duplicate materials are eliminated.
- [ ] Off-screen nodes use `VisibleOnScreenNotifier2D/3D` to pause processing.
- [ ] 3D meshes have LOD enabled via import settings or manual swap logic.
**Physics**
- [ ] Collision layers and masks are minimal — no body checks layers it never needs.
- [ ] No moving body uses `ConcavePolygonShape` — replaced with capsule, box, or convex.
- [ ] Physics tick rate is appropriate for the game type (30 Hz may be fine for turn-based or top-down).
- [ ] `Area2D/3D` is used for overlap detection instead of per-frame raycasts.
- [ ] `RigidBody3D` nodes have `can_sleep = true` where applicable.
**GDScript**
- [ ] No `Array`, `Dictionary`, or `String` is allocated inside `_process` or `_physics_process`.
- [ ] All hot-path string comparisons use `StringName` (`&"..."`).
- [ ] All arrays in hot paths are typed (`Array[T]` or `PackedArray`).
- [ ] All function parameters and return types in hot paths are statically typed.
- [ ] All scene and resource references use `preload` at class scope, not `load` per frame.
**Memory**
- [ ] `Performance.get_monitor(Performance.MEMORY_STATIC)` is stable between scene reloads.
- [ ] Resources that require per-instance mutation are `.duplicate()`d.
- [ ] All node removals use `queue_free()` unless synchronous teardown is explicitly required.
**Object Pooling**
- [ ] Bullets, hit effects, particles, and other frequently spawned objects use a pool.
- [ ] Pool initial size is large enough to avoid runtime growth during normal gameplay.
- [ ] Pooled objects reset all state on reactivation (position, velocity, signals).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!