godot-performance-optimization
godot performance optimization
SKILL.md
Full skill instructions
Godot 4.7 Baseline
- Expert patterns in this skill target Godot 4.7+ (stable, 2026-06-18).
- Consult the Godot 4.7 migration guide when upgrading projects from 4.6.
- NEVER assume 4.6 defaults (stretch mode, audio area_mask, RichTextLabel percent flags) without checking 4.7 migration notes.
Performance Optimization
Profiler-driven analysis, object pooling, and visibility culling define optimized game performance.
Available Scripts
worker_thread_pool_manager.gd
Expert logic for offloading heavy computation to Godot 4's WorkerThreadPool for multi-threaded processing.
object_pool_system.gd
Minimal allocation strategy using node visibility and process toggling instead of constant instantiation.
rendering_server_direct.gd
Bypassing the SceneTree logic for massive canvas item rendering directly via the RenderingServer.
low_level_physics_query.gd
High-performance direct physics space state queries, faster than using RayCast nodes for hundreds of checks.
custom_monitor_profiler.gd
Implementation of real-time performance monitoring using Performance.get_monitor() for bottleneck detection.
manual_culling_logic.gd
Disabling off-screen logic manually using VisibilityNotifiers to cull CPU-heavy processing.
shared_resource_strategy.gd
Expert management of Local-to-Scene vs Shared resources to balance memory usage and unique instance states.
texture_array_batching.gd
Reducing draw calls and state changes by utilizing TextureArrays for multi-item shader-based batching.
multimesh_optimizer.gd
Rendering thousands of animated mesh instances via hardware instancing (MultiMeshInstance3D).
navigation_agent_optimization.gd
Staggered path update strategy for massive AI crowds to prevent pathfinding bottlenecks in a single frame.
NEVER Do in Performance Optimization
- NEVER optimize without profiling first — "I think physics is slow" without data? Premature optimization. ALWAYS use Debug → Profiler (F3) to identify actual bottleneck [20].
- NEVER use
print()in release builds —print()every frame = file I/O bottleneck + log spam. Use@warning_ignoreor conditionalif OS.is_debug_build():[21]. - NEVER ignore
VisibleOnScreenNotifier2Dfor off-screen entities — Enemies processing logic off-screen = wasted CPU. Disableset_process(false)whenscreen_exited[22]. - NEVER instantiate nodes in hot loops —
for i in 1000: var bullet = Bullet.new()= 1000 allocations. Use object pools, reuse instances [23]. - NEVER use
get_node()in_process()— Callingget_node("Player")60x/sec = tree traversal spam. Cache in@onready var player := $Player[24]. - NEVER forget to batch draw calls — 1000 unique sprites = 1000 draw calls. Use TextureAtlas (sprite sheets) + MultiMesh for instanced rendering [25].
- NEVER block the main thread for heavy operations — Avoid
OS.delay_msec()or long synchronous data processing. UseWorkerThreadPoolto keep framerates steady. - NEVER use complex collision shapes for physics queries — High-poly convex shapes are expensive to resolve. Prefer simplified primitives (Circle, Rectangle, Box).
- NEVER forget to disconnect local lambda signals — Anonymous lambdas connected to global signals can cause memory leaks if the capturing object is freed.
- NEVER use large textures without VRAM compression — VRAM is limited. Use S3TC/BPTC for desktop (DirectX/Vulkan) and ETC2 for mobile. Note: Disable compression for Pixel Art to avoid artifacts [13].
- NEVER perform tree modifications during physics steps — Adding/removing nodes during
_inter_rayor_physics_processcan lock the physics server. Usecall_deferred. - NEVER skip shader pre-warming in the Compatibility renderer — Unlike Forward+, OpenGL lacks Ubershaders. Pre-instantiate every mesh/VFX in front of the camera for 1 frame behind a loading screen to avoid hitches [21].
Debug → Profiler (F3)
Tabs:
- Time: Function call times
- Memory: RAM usage
- Network: RPCs, bandwidth
- Physics: Collision checks
Common Optimizations
Object Pooling
var bullet_pool: Array[Node] = []
func get_bullet() -> Node:
if bullet_pool.is_empty():
return Bullet.new()
return bullet_pool.pop_back()
func return_bullet(bullet: Node) -> void:
bullet.hide()
bullet_pool.append(bullet)
Visibility Notifier
# Add VisibleOnScreenNotifier2D
# Disable processing when off-screen
func _on_screen_exited() -> void:
set_process(false)
func _on_screen_entered() -> void:
set_process(true)
AStar-Throttler (Pathfinding Budget)
Spreading pathfinding costs over multiple frames to prevent frame-time spikes.
- Implementation:
var _query_queue: Array[Callable] = [] const TIME_BUDGET_USEC := 1000 # 1ms budget func _process(_delta): var start_time := Time.get_ticks_usec() while not _query_queue.is_empty() and (Time.get_ticks_usec() - start_time) < TIME_BUDGET_USEC: var query = _query_queue.pop_front() query.call()
Shader-Preloading (Zero-Hitch Strategy)
- Forward+ / Mobile: Godot 4.4+ uses Ubershaders for automatic precompilation. Ensure scenes are instantiated at least once (even if invisible) at load-time to trigger pipeline detection [19].
- Compatibility (OpenGL): Place a hidden
Camera3Dlooking at a small area containing every unique mesh and material in your project for 1 frame during the loading screen [20].
VRAM Compression Guide
- S3TC (Desktop): Best for high-quality textures on Windows/Linux/macOS.
- BPTC (Desktop): Superior quality for HDR and normal maps; slightly higher VRAM usage.
- ETC2 (Mobile): The standard for Android/iOS; ensure textures are opaque where possible for maximum compatibility [13].
Reference
Related
- Master Skill: godot-master
