forked from datawhale/whale-town-front
docs:添加性能优化指南
- 创建全面的性能优化文档 - 涵盖渲染、内存、脚本、网络等各方面优化 - 提供具体的代码示例和最佳实践 - 包含性能监控和调试工具使用方法 - 为开发者提供系统的性能优化指导
This commit is contained in:
507
docs/performance_optimization.md
Normal file
507
docs/performance_optimization.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# 性能优化指南
|
||||
|
||||
本文档提供 Whale Town 项目的性能优化策略和最佳实践。
|
||||
|
||||
## 🎯 优化目标
|
||||
|
||||
### 性能指标
|
||||
- **帧率**: 保持60FPS稳定运行
|
||||
- **内存使用**: 控制在合理范围内
|
||||
- **加载时间**: 场景切换<2秒
|
||||
- **响应时间**: UI交互<100ms
|
||||
|
||||
### 优化原则
|
||||
1. **测量优先** - 先测量再优化
|
||||
2. **渐进优化** - 逐步改进性能
|
||||
3. **平衡取舍** - 在质量和性能间平衡
|
||||
4. **用户体验** - 优化用户感知性能
|
||||
|
||||
## 🔧 渲染优化
|
||||
|
||||
### 纹理优化
|
||||
```gdscript
|
||||
# 使用合适的纹理格式
|
||||
# 小图标使用 RGBA8
|
||||
# 大背景使用 DXT1/DXT5
|
||||
# UI元素使用 RGBA4444
|
||||
|
||||
# 纹理压缩设置
|
||||
func optimize_texture_import():
|
||||
# 在导入设置中启用压缩
|
||||
# 设置合适的最大尺寸
|
||||
# 启用Mipmaps(3D纹理)
|
||||
pass
|
||||
```
|
||||
|
||||
### 批处理优化
|
||||
```gdscript
|
||||
# 合并相同材质的对象
|
||||
# 使用MultiMesh渲染大量相同对象
|
||||
func create_multimesh_instances():
|
||||
var multimesh = MultiMesh.new()
|
||||
multimesh.transform_format = MultiMesh.TRANSFORM_2D
|
||||
multimesh.instance_count = 100
|
||||
|
||||
var mesh_instance = MeshInstance2D.new()
|
||||
mesh_instance.multimesh = multimesh
|
||||
add_child(mesh_instance)
|
||||
```
|
||||
|
||||
### 视锥剔除
|
||||
```gdscript
|
||||
# 只渲染可见区域的对象
|
||||
func update_visibility():
|
||||
var camera = get_viewport().get_camera_2d()
|
||||
var screen_rect = get_viewport_rect()
|
||||
|
||||
for child in get_children():
|
||||
if child is Node2D:
|
||||
var visible = screen_rect.intersects(child.get_rect())
|
||||
child.visible = visible
|
||||
```
|
||||
|
||||
## 💾 内存优化
|
||||
|
||||
### 对象池模式
|
||||
```gdscript
|
||||
# ObjectPool.gd
|
||||
class_name ObjectPool
|
||||
extends Node
|
||||
|
||||
var pools: Dictionary = {}
|
||||
|
||||
func get_object(type: String) -> Node:
|
||||
if not pools.has(type):
|
||||
pools[type] = []
|
||||
|
||||
var pool = pools[type]
|
||||
if pool.size() > 0:
|
||||
return pool.pop_back()
|
||||
else:
|
||||
return _create_new_object(type)
|
||||
|
||||
func return_object(obj: Node, type: String):
|
||||
obj.reset() # 重置对象状态
|
||||
pools[type].append(obj)
|
||||
|
||||
func _create_new_object(type: String) -> Node:
|
||||
match type:
|
||||
"Bullet":
|
||||
return preload("res://prefabs/Bullet.tscn").instantiate()
|
||||
"Enemy":
|
||||
return preload("res://prefabs/Enemy.tscn").instantiate()
|
||||
_:
|
||||
return null
|
||||
```
|
||||
|
||||
### 资源管理
|
||||
```gdscript
|
||||
# ResourceManager.gd
|
||||
class_name ResourceManager
|
||||
extends Node
|
||||
|
||||
var loaded_resources: Dictionary = {}
|
||||
var resource_usage: Dictionary = {}
|
||||
|
||||
func load_resource(path: String) -> Resource:
|
||||
if loaded_resources.has(path):
|
||||
resource_usage[path] += 1
|
||||
return loaded_resources[path]
|
||||
|
||||
var resource = load(path)
|
||||
loaded_resources[path] = resource
|
||||
resource_usage[path] = 1
|
||||
return resource
|
||||
|
||||
func unload_resource(path: String):
|
||||
if resource_usage.has(path):
|
||||
resource_usage[path] -= 1
|
||||
if resource_usage[path] <= 0:
|
||||
loaded_resources.erase(path)
|
||||
resource_usage.erase(path)
|
||||
```
|
||||
|
||||
### 内存监控
|
||||
```gdscript
|
||||
# MemoryMonitor.gd
|
||||
extends Node
|
||||
|
||||
func _ready():
|
||||
# 每秒检查一次内存使用
|
||||
var timer = Timer.new()
|
||||
timer.wait_time = 1.0
|
||||
timer.timeout.connect(_check_memory)
|
||||
add_child(timer)
|
||||
timer.start()
|
||||
|
||||
func _check_memory():
|
||||
var memory_usage = OS.get_static_memory_usage_by_type()
|
||||
var total_memory = OS.get_static_memory_peak_usage()
|
||||
|
||||
print("内存使用: ", memory_usage / 1024 / 1024, "MB")
|
||||
print("峰值内存: ", total_memory / 1024 / 1024, "MB")
|
||||
|
||||
# 内存使用过高时触发垃圾回收
|
||||
if total_memory > 200 * 1024 * 1024: # 200MB
|
||||
print("触发垃圾回收")
|
||||
# 清理不必要的资源
|
||||
_cleanup_resources()
|
||||
|
||||
func _cleanup_resources():
|
||||
# 清理缓存
|
||||
# 卸载未使用的资源
|
||||
# 强制垃圾回收
|
||||
pass
|
||||
```
|
||||
|
||||
## ⚡ 脚本优化
|
||||
|
||||
### 避免频繁计算
|
||||
```gdscript
|
||||
# ❌ 错误示例:每帧计算
|
||||
func _process(delta):
|
||||
var distance = global_position.distance_to(target.global_position)
|
||||
if distance < 100:
|
||||
attack_target()
|
||||
|
||||
# ✅ 正确示例:缓存计算结果
|
||||
var cached_distance: float = 0.0
|
||||
var distance_update_timer: float = 0.0
|
||||
|
||||
func _process(delta):
|
||||
distance_update_timer += delta
|
||||
if distance_update_timer >= 0.1: # 每100ms更新一次
|
||||
cached_distance = global_position.distance_to(target.global_position)
|
||||
distance_update_timer = 0.0
|
||||
|
||||
if cached_distance < 100:
|
||||
attack_target()
|
||||
```
|
||||
|
||||
### 优化循环
|
||||
```gdscript
|
||||
# ❌ 错误示例:嵌套循环
|
||||
func find_nearest_enemy():
|
||||
var nearest = null
|
||||
var min_distance = INF
|
||||
|
||||
for enemy in enemies:
|
||||
for player in players:
|
||||
var distance = enemy.global_position.distance_to(player.global_position)
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
nearest = enemy
|
||||
|
||||
# ✅ 正确示例:优化算法
|
||||
func find_nearest_enemy():
|
||||
var player_pos = player.global_position
|
||||
var nearest = null
|
||||
var min_distance = INF
|
||||
|
||||
for enemy in enemies:
|
||||
var distance = enemy.global_position.distance_squared_to(player_pos)
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
nearest = enemy
|
||||
```
|
||||
|
||||
### 事件优化
|
||||
```gdscript
|
||||
# 使用信号代替轮询
|
||||
# ❌ 错误示例:轮询检查
|
||||
func _process(delta):
|
||||
if player.health <= 0:
|
||||
game_over()
|
||||
|
||||
# ✅ 正确示例:事件驱动
|
||||
func _ready():
|
||||
player.health_changed.connect(_on_health_changed)
|
||||
|
||||
func _on_health_changed(new_health: int):
|
||||
if new_health <= 0:
|
||||
game_over()
|
||||
```
|
||||
|
||||
## 🎮 游戏逻辑优化
|
||||
|
||||
### 状态机优化
|
||||
```gdscript
|
||||
# StateMachine.gd
|
||||
class_name StateMachine
|
||||
extends Node
|
||||
|
||||
var current_state: State
|
||||
var states: Dictionary = {}
|
||||
|
||||
func change_state(state_name: String):
|
||||
if current_state:
|
||||
current_state.exit()
|
||||
|
||||
current_state = states[state_name]
|
||||
current_state.enter()
|
||||
|
||||
func _process(delta):
|
||||
if current_state:
|
||||
current_state.update(delta)
|
||||
```
|
||||
|
||||
### AI优化
|
||||
```gdscript
|
||||
# EnemyAI.gd
|
||||
extends Node
|
||||
|
||||
var update_interval: float = 0.2 # 每200ms更新一次AI
|
||||
var update_timer: float = 0.0
|
||||
|
||||
func _process(delta):
|
||||
update_timer += delta
|
||||
if update_timer >= update_interval:
|
||||
update_ai()
|
||||
update_timer = 0.0
|
||||
|
||||
func update_ai():
|
||||
# AI逻辑更新
|
||||
# 路径寻找
|
||||
# 决策制定
|
||||
pass
|
||||
```
|
||||
|
||||
### 碰撞检测优化
|
||||
```gdscript
|
||||
# 使用空间分区优化碰撞检测
|
||||
class_name SpatialGrid
|
||||
extends Node
|
||||
|
||||
var grid_size: int = 64
|
||||
var grid: Dictionary = {}
|
||||
|
||||
func add_object(obj: Node2D):
|
||||
var grid_pos = Vector2(
|
||||
int(obj.global_position.x / grid_size),
|
||||
int(obj.global_position.y / grid_size)
|
||||
)
|
||||
|
||||
if not grid.has(grid_pos):
|
||||
grid[grid_pos] = []
|
||||
|
||||
grid[grid_pos].append(obj)
|
||||
|
||||
func get_nearby_objects(pos: Vector2) -> Array:
|
||||
var grid_pos = Vector2(
|
||||
int(pos.x / grid_size),
|
||||
int(pos.y / grid_size)
|
||||
)
|
||||
|
||||
var nearby = []
|
||||
for x in range(-1, 2):
|
||||
for y in range(-1, 2):
|
||||
var check_pos = grid_pos + Vector2(x, y)
|
||||
if grid.has(check_pos):
|
||||
nearby.append_array(grid[check_pos])
|
||||
|
||||
return nearby
|
||||
```
|
||||
|
||||
## 🌐 网络优化
|
||||
|
||||
### 数据压缩
|
||||
```gdscript
|
||||
# NetworkManager.gd
|
||||
func send_player_data(data: Dictionary):
|
||||
# 只发送变化的数据
|
||||
var delta_data = get_changed_data(data)
|
||||
|
||||
# 压缩数据
|
||||
var compressed = compress_data(delta_data)
|
||||
|
||||
# 发送数据
|
||||
send_to_server(compressed)
|
||||
|
||||
func compress_data(data: Dictionary) -> PackedByteArray:
|
||||
var json_string = JSON.stringify(data)
|
||||
var bytes = json_string.to_utf8_buffer()
|
||||
return bytes.compress(FileAccess.COMPRESSION_GZIP)
|
||||
```
|
||||
|
||||
### 批量更新
|
||||
```gdscript
|
||||
# 批量发送网络更新
|
||||
var pending_updates: Array = []
|
||||
var update_timer: float = 0.0
|
||||
|
||||
func _process(delta):
|
||||
update_timer += delta
|
||||
if update_timer >= 0.05: # 每50ms发送一次
|
||||
if pending_updates.size() > 0:
|
||||
send_batch_updates(pending_updates)
|
||||
pending_updates.clear()
|
||||
update_timer = 0.0
|
||||
|
||||
func queue_update(update_data: Dictionary):
|
||||
pending_updates.append(update_data)
|
||||
```
|
||||
|
||||
## 📊 性能监控
|
||||
|
||||
### FPS监控
|
||||
```gdscript
|
||||
# FPSMonitor.gd
|
||||
extends Control
|
||||
|
||||
@onready var fps_label: Label = $FPSLabel
|
||||
var fps_history: Array = []
|
||||
|
||||
func _process(delta):
|
||||
var current_fps = Engine.get_frames_per_second()
|
||||
fps_history.append(current_fps)
|
||||
|
||||
if fps_history.size() > 60: # 保留60帧历史
|
||||
fps_history.pop_front()
|
||||
|
||||
var avg_fps = 0
|
||||
for fps in fps_history:
|
||||
avg_fps += fps
|
||||
avg_fps /= fps_history.size()
|
||||
|
||||
fps_label.text = "FPS: %d (平均: %d)" % [current_fps, avg_fps]
|
||||
|
||||
# FPS过低时警告
|
||||
if avg_fps < 30:
|
||||
modulate = Color.RED
|
||||
elif avg_fps < 50:
|
||||
modulate = Color.YELLOW
|
||||
else:
|
||||
modulate = Color.WHITE
|
||||
```
|
||||
|
||||
### 性能分析器
|
||||
```gdscript
|
||||
# Profiler.gd
|
||||
class_name Profiler
|
||||
extends Node
|
||||
|
||||
var timers: Dictionary = {}
|
||||
|
||||
func start_timer(name: String):
|
||||
timers[name] = Time.get_time_dict_from_system()
|
||||
|
||||
func end_timer(name: String) -> float:
|
||||
if not timers.has(name):
|
||||
return 0.0
|
||||
|
||||
var start_time = timers[name]
|
||||
var end_time = Time.get_time_dict_from_system()
|
||||
|
||||
var duration = (end_time.hour * 3600 + end_time.minute * 60 + end_time.second) - \
|
||||
(start_time.hour * 3600 + start_time.minute * 60 + start_time.second)
|
||||
|
||||
timers.erase(name)
|
||||
return duration
|
||||
|
||||
# 使用示例
|
||||
func expensive_function():
|
||||
Profiler.start_timer("expensive_function")
|
||||
|
||||
# 执行耗时操作
|
||||
for i in range(10000):
|
||||
pass
|
||||
|
||||
var duration = Profiler.end_timer("expensive_function")
|
||||
print("函数执行时间: ", duration, "秒")
|
||||
```
|
||||
|
||||
## 🛠️ 调试工具
|
||||
|
||||
### 性能调试面板
|
||||
```gdscript
|
||||
# DebugPanel.gd
|
||||
extends Control
|
||||
|
||||
@onready var memory_label: Label = $VBox/MemoryLabel
|
||||
@onready var fps_label: Label = $VBox/FPSLabel
|
||||
@onready var objects_label: Label = $VBox/ObjectsLabel
|
||||
|
||||
func _process(delta):
|
||||
# 更新内存使用
|
||||
var memory = OS.get_static_memory_usage_by_type()
|
||||
memory_label.text = "内存: %.1f MB" % (memory / 1024.0 / 1024.0)
|
||||
|
||||
# 更新FPS
|
||||
fps_label.text = "FPS: %d" % Engine.get_frames_per_second()
|
||||
|
||||
# 更新对象数量
|
||||
var object_count = get_tree().get_node_count()
|
||||
objects_label.text = "对象数: %d" % object_count
|
||||
```
|
||||
|
||||
### 热点分析
|
||||
```gdscript
|
||||
# HotspotAnalyzer.gd
|
||||
extends Node
|
||||
|
||||
var function_calls: Dictionary = {}
|
||||
var function_times: Dictionary = {}
|
||||
|
||||
func profile_function(func_name: String, callable: Callable):
|
||||
var start_time = Time.get_time_dict_from_system()
|
||||
|
||||
callable.call()
|
||||
|
||||
var end_time = Time.get_time_dict_from_system()
|
||||
var duration = (end_time.hour * 3600 + end_time.minute * 60 + end_time.second) - \
|
||||
(start_time.hour * 3600 + start_time.minute * 60 + start_time.second)
|
||||
|
||||
if not function_calls.has(func_name):
|
||||
function_calls[func_name] = 0
|
||||
function_times[func_name] = 0.0
|
||||
|
||||
function_calls[func_name] += 1
|
||||
function_times[func_name] += duration
|
||||
|
||||
func print_profile_report():
|
||||
print("=== 性能分析报告 ===")
|
||||
for func_name in function_calls.keys():
|
||||
var calls = function_calls[func_name]
|
||||
var total_time = function_times[func_name]
|
||||
var avg_time = total_time / calls
|
||||
|
||||
print("%s: 调用%d次, 总时间%.3fs, 平均%.3fs" % [func_name, calls, total_time, avg_time])
|
||||
```
|
||||
|
||||
## 📚 最佳实践
|
||||
|
||||
### 开发阶段
|
||||
1. **早期优化** - 在设计阶段考虑性能
|
||||
2. **渐进开发** - 逐步添加功能并测试性能
|
||||
3. **定期测试** - 定期进行性能测试
|
||||
4. **文档记录** - 记录性能优化决策
|
||||
|
||||
### 测试阶段
|
||||
1. **多设备测试** - 在不同性能设备上测试
|
||||
2. **压力测试** - 测试极限情况下的性能
|
||||
3. **长时间测试** - 测试内存泄漏和性能衰减
|
||||
4. **用户测试** - 收集真实用户的性能反馈
|
||||
|
||||
### 发布阶段
|
||||
1. **性能监控** - 监控线上性能指标
|
||||
2. **快速响应** - 快速修复性能问题
|
||||
3. **持续优化** - 根据数据持续优化
|
||||
4. **版本对比** - 对比不同版本的性能表现
|
||||
|
||||
## 🔍 常见问题
|
||||
|
||||
### Q: 如何识别性能瓶颈?
|
||||
A: 使用Godot的内置分析器,添加自定义性能监控,分析FPS和内存使用情况。
|
||||
|
||||
### Q: 内存使用过高怎么办?
|
||||
A: 检查资源加载,使用对象池,及时释放不需要的对象,优化纹理大小。
|
||||
|
||||
### Q: 如何优化大量对象的渲染?
|
||||
A: 使用MultiMesh批处理,实现视锥剔除,使用LOD系统,合并相同材质的对象。
|
||||
|
||||
### Q: 网络延迟如何优化?
|
||||
A: 减少网络请求频率,压缩传输数据,使用预测和插值,实现客户端预测。
|
||||
|
||||
---
|
||||
|
||||
**记住:性能优化是一个持续的过程,需要在开发的各个阶段都保持关注!**
|
||||
Reference in New Issue
Block a user