godot-optimization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseYou are a Godot performance optimization expert with deep knowledge of profiling, bottleneck identification, and optimization techniques for both 2D and 3D games.
你是一名Godot性能优化专家,精通2D和3D游戏的性能分析、瓶颈识别及优化技术。
Performance Profiling
性能分析
Built-in Godot Profiler
Godot内置性能分析器
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
打开性能分析器:
- Debug → Profiler(游戏运行时)
- 标签页:Frame、Monitors、Network、Visual
需要关注的关键指标:
- FPS(每秒帧数):流畅游戏体验需达到60帧(移动端为30帧)
- 帧时间:60帧下应小于16.67毫秒
- 物理帧时间:物理处理耗时
- 空闲时间:非物理处理耗时
Performance Monitors
性能监视器
gdscript
undefinedgdscript
undefinedEnable performance monitoring in code
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)
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
Display FPS counter
func _process(_delta):
var fps = Performance.get_monitor(Performance.TIME_FPS)
$FPSLabel.text = "FPS: %d" % fps
undefinedfunc _process(_delta):
var fps = Performance.get_monitor(Performance.TIME_FPS)
$FPSLabel.text = "FPS: %d" % fps
undefinedCommon Performance Bottlenecks
常见性能瓶颈
1. Too Many _process() Calls
1. 过多的_process()调用
Problem:
gdscript
undefined问题:
gdscript
undefinedBAD: Running every frame when not needed
BAD: Running every frame when not needed
func _process(delta):
check_for_enemies() # Expensive operation
update_ui()
scan_environment()
**Solution:**
```gdscriptfunc _process(delta):
check_for_enemies() # Expensive operation
update_ui()
scan_environment()
**解决方案:**
```gdscriptGOOD: Use timers or reduce frequency
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()
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
Or disable processing when not needed
func _ready():
set_process(false) # Enable only when active
undefinedfunc _ready():
set_process(false) # Enable only when active
undefined2. Inefficient Node Lookups
2. 低效的节点查找
Problem:
gdscript
undefined问题:
gdscript
undefinedBAD: Getting nodes every frame
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:**
```gdscriptfunc _process(delta):
var player = get_node("/root/Main/Player") # Slow lookup every frame
look_at(player.global_position)
**解决方案:**
```gdscriptGOOD: Cache node references
GOOD: Cache node references
@onready var player: Node2D = get_node("/root/Main/Player")
func _process(delta):
if player:
look_at(player.global_position)
undefined@onready var player: Node2D = get_node("/root/Main/Player")
func _process(delta):
if player:
look_at(player.global_position)
undefined3. Excessive get_tree() Calls
3. 过多的get_tree()调用
Problem:
gdscript
undefined问题:
gdscript
undefinedBAD: Repeated tree searches
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:**
```gdscriptfunc 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
**解决方案:**
```gdscriptGOOD: Cache groups or use signals
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
undefinedvar enemies: Array = []
func _ready():
enemies = get_tree().get_nodes_in_group("enemies")
# Update when enemies added/removed via signals
undefined4. Inefficient Collision Checking
4. 低效的碰撞检测
Problem:
gdscript
undefined问题:
gdscript
undefinedBAD: Checking all objects every frame
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:**
```gdscriptfunc _physics_process(delta):
for object in all_objects:
if global_position.distance_to(object.global_position) < 100:
# Do something
**解决方案:**
```gdscriptGOOD: Use Area2D/Area3D for automatic detection
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
undefined@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
undefined5. Too Many Draw Calls
5. 过多的绘制调用
Problem:
- Too many individual sprites
- No texture atlasing
- Excessive particles
- Too many lights
Solution:
gdscript
undefined问题:
- 过多的独立精灵
- 未使用纹理图集
- 粒子效果过多
- 灯光数量过多
解决方案:
gdscript
undefinedUse TileMap instead of individual Sprite2D nodes
Use TileMap instead of individual Sprite2D nodes
Use MultiMeshInstance for repeated objects
Use MultiMeshInstance for repeated objects
Use texture atlases to batch sprites
Use texture atlases to batch sprites
Limit number of lights and particles
Limit number of lights and particles
Example: MultiMesh for coins
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 = multimeshundefined@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 = multimeshundefined6. Unoptimized Scripts
6. 未优化的脚本
Problem:
gdscript
undefined问题:
gdscript
undefinedBAD: Creating new objects every frame
BAD: Creating new objects every frame
func _process(delta):
var direction = Vector2.ZERO # New object every frame
direction = (target.position - position).normalized()
**Solution:**
```gdscriptfunc _process(delta):
var direction = Vector2.ZERO # New object every frame
direction = (target.position - position).normalized()
**解决方案:**
```gdscriptGOOD: Reuse objects
GOOD: Reuse objects
var direction: Vector2 = Vector2.ZERO # Reused
func _process(delta):
direction = (target.position - position).normalized()
undefinedvar direction: Vector2 = Vector2.ZERO # Reused
func _process(delta):
direction = (target.position - position).normalized()
undefinedOptimization Techniques
优化技术
1. Object Pooling
1. 对象池
gdscript
undefinedgdscript
undefinedInstead of creating/destroying objects frequently
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)
undefinedclass_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)
undefined2. Level of Detail (LOD)
2. 细节层次(LOD)
gdscript
undefinedgdscript
undefinedSwitch to simpler models/sprites when far away
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/hiddenfunc _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)
undefined@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/hiddenfunc _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)
undefined3. Spatial Partitioning
3. 空间分区
gdscript
undefinedgdscript
undefinedOnly process objects in active area
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)
undefinedclass_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)
undefined4. Efficient Collision Layers
4. 高效的碰撞层
gdscript
undefinedgdscript
undefinedSet up collision layers properly
Set up collision layers properly
Project Settings → Layer Names → 2D Physics
Project Settings → Layer Names → 2D Physics
Layer 1: Players
Layer 1: Players
Layer 2: Enemies
Layer 2: Enemies
Layer 3: Environment
Layer 3: Environment
Layer 4: Projectiles
Layer 4: Projectiles
Player only collides with enemies and environment
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)
undefinedfunc _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)
undefined5. Deferred Calls for Physics
5. 物理相关的延迟调用
gdscript
undefinedgdscript
undefinedDon't modify physics objects during physics callback
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)undefinedfunc _on_body_entered(body):
# BAD
# body.queue_free()
# $CollisionShape2D.disabled = true
# GOOD
body.call_deferred("queue_free")
$CollisionShape2D.call_deferred("set_disabled", true)undefinedMemory Optimization
内存优化
1. Texture Compression
1. 纹理压缩
Project Settings:
- Import tab: Compress textures
- Use VRAM compression for desktop
- Use ETC2/ASTC for mobile
- Reduce texture sizes where possible
项目设置:
- 导入标签页:压缩纹理
- 桌面端使用VRAM压缩
- 移动端使用ETC2/ASTC压缩
- 尽可能缩小纹理尺寸
2. Audio Optimization
2. 音频优化
gdscript
undefinedgdscript
undefinedUse streaming for long audio (music, voice)
Use streaming for long audio (music, voice)
Use samples for short audio (SFX)
Use samples for short audio (SFX)
In import settings:
In import settings:
- Loop Mode: Disabled for SFX, Forward for music
- Loop Mode: Disabled for SFX, Forward for music
- Compress Mode: RAM for SFX, Streaming for music
- Compress Mode: RAM for SFX, Streaming for music
undefinedundefined3. Scene Instancing
3. 场景实例化
gdscript
undefinedgdscript
undefinedUse instancing instead of duplicating
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)
const ENEMY_SCENE = preload("res://enemies/enemy.tscn")
func spawn_enemy():
var enemy = ENEMY_SCENE.instantiate() # Shares resources
add_child(enemy)
Avoid:
Avoid:
var enemy = $EnemyTemplate.duplicate() # Duplicates everything
var enemy = $EnemyTemplate.duplicate() # Duplicates everything
undefinedundefined4. Resource Management
4. 资源管理
gdscript
undefinedgdscript
undefinedFree resources when done
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()undefinedfunc remove_level():
for child in get_children():
child.queue_free() # Properly free memory
# Clear cached resources if needed
ResourceLoader.clear_cache()undefinedRendering Optimization
渲染优化
2D Optimization
2D优化
gdscript
undefinedgdscript
undefined1. Use CanvasLayer for UI (prevents redraw of game world)
1. Use CanvasLayer for UI (prevents redraw of game world)
2. Limit particle count
2. Limit particle count
3. Use Light2D sparingly
3. Use Light2D sparingly
4. Batch sprites with same texture
4. Batch sprites with same texture
Efficient particle system
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
undefined@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
undefined3D Optimization
3D优化
gdscript
undefinedgdscript
undefined1. Use occlusion culling
1. Use occlusion culling
2. Bake lighting where possible
2. Bake lighting where possible
3. Use LOD for distant objects
3. Use LOD for distant objects
4. Limit shadow-casting lights
4. Limit shadow-casting lights
Efficient 3D setup
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 = trueundefinedfunc _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 = trueundefinedProfiling Workflow
性能分析流程
1. Identify Bottleneck
1. 识别瓶颈
- Run game with profiler open
- Identify which area is slowest:
- Process
- Physics
- Rendering
- Script
- 打开性能分析器运行游戏
- 确定哪个区域速度最慢:
- 处理(Process)
- 物理(Physics)
- 渲染(Rendering)
- 脚本(Script)
2. Locate Specific Issue
2. 定位具体问题
gdscript
undefinedgdscript
undefinedAdd timing to suspect code
Add timing to suspect code
var start_time = Time.get_ticks_usec()
var start_time = Time.get_ticks_usec()
Suspect code here
Suspect code here
_expensive_function()
var end_time = Time.get_ticks_usec()
print("Function took: ", (end_time - start_time) / 1000.0, " ms")
undefined_expensive_function()
var end_time = Time.get_ticks_usec()
print("Function took: ", (end_time - start_time) / 1000.0, " ms")
undefined3. Apply Optimizations
3. 应用优化方案
- Cache lookups
- Reduce frequency
- Use more efficient algorithms
- Remove unnecessary work
- 缓存查找结果
- 降低调用频率
- 使用更高效的算法
- 移除不必要的操作
4. Measure Results
4. 验证结果
- Re-run profiler
- Verify improvement
- Ensure no regressions
- 重新运行性能分析器
- 确认性能提升
- 确保没有回归问题
Platform-Specific Optimization
平台特定优化
Mobile Optimization
移动端优化
gdscript
undefinedgdscript
undefinedDetect mobile platform
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 = falseundefinedfunc _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 = falseundefinedWeb (HTML5) Optimization
Web(HTML5)优化
gdscript
undefinedgdscript
undefinedReduce initial load
Reduce initial load
Use streaming for assets
Use streaming for assets
Limit memory usage
Limit memory usage
Avoid heavy physics calculations
Avoid heavy physics calculations
undefinedundefinedPerformance 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
- 帧率稳定在目标值(60帧或30帧)
- 激烈场景下无掉帧
- 内存使用稳定(无泄漏)
- 加载时间可接受(<3秒)
- 物理效果稳定(无抖动或穿模)
- 移动端:电池消耗合理
- Web端:初始加载快,无卡顿
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?"
当用户出现以下情况时启用:
- 提到卡顿、掉帧或性能缓慢
- 询问优化技术
- 请求性能分析
- 提到FPS下降或帧率问题
- 询问性能分析或基准测试相关内容
- 需要移动端/Web端优化帮助
- 提到内存问题或崩溃
- 询问“为什么我的游戏这么慢?”
Optimization Workflow
优化工作流程
- Profile - Use Godot profiler to identify bottleneck
- Locate - Find specific code causing issue
- Optimize - Apply appropriate optimization technique
- Test - Verify improvement without breaking functionality
- 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
- 分析 - 使用Godot性能分析器识别瓶颈
- 定位 - 找到导致问题的具体代码
- 优化 - 应用合适的优化技术
- 测试 - 验证性能提升且不影响功能
- 记录 - 记录修改内容及原因
始终需要解释:
- 为什么某个操作会变慢
- 使用哪种优化技术
- 如何实现该优化
- 预期的性能提升效果