Files
whale-town-front/docs/performance_optimization.md
moyin c0f5d6a537 docs:添加性能优化指南
- 创建全面的性能优化文档
- 涵盖渲染、内存、脚本、网络等各方面优化
- 提供具体的代码示例和最佳实践
- 包含性能监控和调试工具使用方法
- 为开发者提供系统的性能优化指导
2025-12-24 20:51:45 +08:00

13 KiB
Raw Blame History

性能优化指南

本文档提供 Whale Town 项目的性能优化策略和最佳实践。

🎯 优化目标

性能指标

  • 帧率: 保持60FPS稳定运行
  • 内存使用: 控制在合理范围内
  • 加载时间: 场景切换<2秒
  • 响应时间: UI交互<100ms

优化原则

  1. 测量优先 - 先测量再优化
  2. 渐进优化 - 逐步改进性能
  3. 平衡取舍 - 在质量和性能间平衡
  4. 用户体验 - 优化用户感知性能

🔧 渲染优化

纹理优化

# 使用合适的纹理格式
# 小图标使用 RGBA8
# 大背景使用 DXT1/DXT5
# UI元素使用 RGBA4444

# 纹理压缩设置
func optimize_texture_import():
    # 在导入设置中启用压缩
    # 设置合适的最大尺寸
    # 启用Mipmaps3D纹理
    pass

批处理优化

# 合并相同材质的对象
# 使用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)

视锥剔除

# 只渲染可见区域的对象
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

💾 内存优化

对象池模式

# 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

资源管理

# 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)

内存监控

# 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

脚本优化

避免频繁计算

# ❌ 错误示例:每帧计算
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()

优化循环

# ❌ 错误示例:嵌套循环
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

事件优化

# 使用信号代替轮询
# ❌ 错误示例:轮询检查
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()

🎮 游戏逻辑优化

状态机优化

# 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优化

# 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

碰撞检测优化

# 使用空间分区优化碰撞检测
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

🌐 网络优化

数据压缩

# 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)

批量更新

# 批量发送网络更新
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监控

# 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

性能分析器

# 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, "秒")

🛠️ 调试工具

性能调试面板

# 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

热点分析

# 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: 减少网络请求频率,压缩传输数据,使用预测和插值,实现客户端预测。


记住:性能优化是一个持续的过程,需要在开发的各个阶段都保持关注!