新的目录结构: 01-项目入门/ # 新人必读,项目基础 02-开发规范/ # 编码标准和规范 03-技术实现/ # 具体开发指导 04-高级开发/ # 进阶开发技巧 05-部署运维/ # 发布和部署 06-功能模块/ # 特定功能文档 新增导航文档: - docs/README.md - 完整的文档导航和使用指南 - 各目录下的README.md - 分类说明和使用指导 优化效果: - 开发者可以按阶段快速定位需要的文档 - 新人有清晰的学习路径 - 不同角色有针对性的文档推荐 - 提供了问题导向的快速查找功能
507 lines
13 KiB
Markdown
507 lines
13 KiB
Markdown
# 性能优化指南
|
||
|
||
本文档提供 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: 减少网络请求频率,压缩传输数据,使用预测和插值,实现客户端预测。
|
||
|
||
---
|
||
|
||
**记住:性能优化是一个持续的过程,需要在开发的各个阶段都保持关注!** |