Merge branch 'develop' into feature/存档功能更新
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用对象池管理器
|
||||
/// </summary>
|
||||
public class ObjectPool : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class PoolItem
|
||||
{
|
||||
public string tag;
|
||||
public GameObject prefab;
|
||||
public int size;
|
||||
}
|
||||
|
||||
[Header("对象池配置")]
|
||||
public List<PoolItem> poolItems = new List<PoolItem>();
|
||||
|
||||
private Dictionary<string, Queue<GameObject>> poolDictionary;
|
||||
private Dictionary<string, GameObject> prefabDictionary;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializePools();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化所有对象池
|
||||
/// </summary>
|
||||
private void InitializePools()
|
||||
{
|
||||
poolDictionary = new Dictionary<string, Queue<GameObject>>();
|
||||
prefabDictionary = new Dictionary<string, GameObject>();
|
||||
|
||||
foreach (PoolItem item in poolItems)
|
||||
{
|
||||
Queue<GameObject> objectPool = new Queue<GameObject>();
|
||||
|
||||
for (int i = 0; i < item.size; i++)
|
||||
{
|
||||
GameObject obj = CreateNewObject(item.prefab, item.tag);
|
||||
objectPool.Enqueue(obj);
|
||||
}
|
||||
|
||||
poolDictionary.Add(item.tag, objectPool);
|
||||
prefabDictionary.Add(item.tag, item.prefab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新对象
|
||||
/// </summary>
|
||||
private GameObject CreateNewObject(GameObject prefab, string tag)
|
||||
{
|
||||
GameObject obj = Instantiate(prefab);
|
||||
obj.SetActive(false);
|
||||
|
||||
// 添加池化组件
|
||||
PooledObject pooledObj = obj.GetComponent<PooledObject>();
|
||||
if (pooledObj == null)
|
||||
{
|
||||
pooledObj = obj.AddComponent<PooledObject>();
|
||||
}
|
||||
pooledObj.poolTag = tag;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从对象池获取对象
|
||||
/// </summary>
|
||||
public GameObject GetFromPool(string tag, Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (!poolDictionary.ContainsKey(tag))
|
||||
{
|
||||
Debug.LogWarning($"对象池中不存在标签: {tag}");
|
||||
return null;
|
||||
}
|
||||
|
||||
Queue<GameObject> pool = poolDictionary[tag];
|
||||
GameObject obj;
|
||||
|
||||
if (pool.Count > 0)
|
||||
{
|
||||
obj = pool.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果池为空,创建新对象
|
||||
obj = CreateNewObject(prefabDictionary[tag], tag);
|
||||
}
|
||||
|
||||
obj.SetActive(true);
|
||||
obj.transform.position = position;
|
||||
obj.transform.rotation = rotation;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从对象池获取对象(使用默认位置和旋转)
|
||||
/// </summary>
|
||||
public GameObject GetFromPool(string tag)
|
||||
{
|
||||
return GetFromPool(tag, Vector3.zero, Quaternion.identity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象返回池中
|
||||
/// </summary>
|
||||
public void ReturnToPool(GameObject obj)
|
||||
{
|
||||
PooledObject pooledObj = obj.GetComponent<PooledObject>();
|
||||
if (pooledObj == null)
|
||||
{
|
||||
Debug.LogWarning("对象没有PooledObject组件,无法返回池中");
|
||||
return;
|
||||
}
|
||||
|
||||
string tag = pooledObj.poolTag;
|
||||
if (!poolDictionary.ContainsKey(tag))
|
||||
{
|
||||
Debug.LogWarning($"对象池中不存在标签: {tag}");
|
||||
return;
|
||||
}
|
||||
|
||||
obj.SetActive(false);
|
||||
poolDictionary[tag].Enqueue(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 延迟返回对象到池中
|
||||
/// </summary>
|
||||
public void ReturnToPool(GameObject obj, float delay)
|
||||
{
|
||||
StartCoroutine(ReturnToPoolCoroutine(obj, delay));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ReturnToPoolCoroutine(GameObject obj, float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
ReturnToPool(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空指定对象池
|
||||
/// </summary>
|
||||
public void ClearPool(string tag)
|
||||
{
|
||||
if (!poolDictionary.ContainsKey(tag))
|
||||
{
|
||||
Debug.LogWarning($"对象池中不存在标签: {tag}");
|
||||
return;
|
||||
}
|
||||
|
||||
Queue<GameObject> pool = poolDictionary[tag];
|
||||
while (pool.Count > 0)
|
||||
{
|
||||
GameObject obj = pool.Dequeue();
|
||||
if (obj != null)
|
||||
{
|
||||
Destroy(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有对象池
|
||||
/// </summary>
|
||||
public void ClearAllPools()
|
||||
{
|
||||
foreach (string tag in poolDictionary.Keys)
|
||||
{
|
||||
ClearPool(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取池中对象数量
|
||||
/// </summary>
|
||||
public int GetPoolSize(string tag)
|
||||
{
|
||||
if (!poolDictionary.ContainsKey(tag))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return poolDictionary[tag].Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预加载对象到池中
|
||||
/// </summary>
|
||||
public void PreloadObjects(string tag, int count)
|
||||
{
|
||||
if (!prefabDictionary.ContainsKey(tag))
|
||||
{
|
||||
Debug.LogWarning($"预制体字典中不存在标签: {tag}");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GameObject obj = CreateNewObject(prefabDictionary[tag], tag);
|
||||
poolDictionary[tag].Enqueue(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 池化对象组件
|
||||
/// </summary>
|
||||
public class PooledObject : MonoBehaviour
|
||||
{
|
||||
[HideInInspector]
|
||||
public string poolTag;
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// 当对象被禁用时,自动返回池中
|
||||
// 注意:这可能会导致问题,如果对象被手动禁用
|
||||
// 建议在需要时手动调用ReturnToPool
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7618d3a010d063748970d14e3f54fa10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,238 @@
|
||||
# Unity游戏性能优化指南
|
||||
|
||||
## 概述
|
||||
本文档提供了针对您的Unity游戏的性能优化建议和解决方案。
|
||||
|
||||
## 已发现的性能问题
|
||||
|
||||
### 1. FindObjectOfType过度使用
|
||||
**问题位置:**
|
||||
- `ClueManager.cs` - 第24行
|
||||
- `MemoryClueSlot.cs` - 第13行
|
||||
- `ClueSlot.cs` - 第23行
|
||||
- `ClueInteraction.cs` - 第34行
|
||||
- `MemoryProcess.cs` - 第87、96行
|
||||
|
||||
**解决方案:**
|
||||
- 在Start/Awake中缓存组件引用
|
||||
- 使用单例模式或依赖注入
|
||||
- 避免在Update中调用FindObjectOfType
|
||||
|
||||
### 2. Update方法中的复杂计算
|
||||
**问题位置:**
|
||||
- `UIManager.cs` - 第27行
|
||||
- `MainPanel.cs` - 第29行
|
||||
|
||||
**解决方案:**
|
||||
- 使用协程替代Update中的复杂逻辑
|
||||
- 减少每帧的计算量
|
||||
- 使用事件驱动架构
|
||||
|
||||
### 3. 频繁的对象创建和销毁
|
||||
**问题位置:**
|
||||
- `ClueManager.cs` - 第35、58行(Instantiate调用)
|
||||
|
||||
**解决方案:**
|
||||
- 使用对象池管理频繁创建的对象
|
||||
- 预加载常用资源
|
||||
- 复用对象而不是重新创建
|
||||
|
||||
## 性能优化工具
|
||||
|
||||
### 1. PerformanceProfiler
|
||||
实时监控游戏性能指标:
|
||||
- FPS监控
|
||||
- 内存使用情况
|
||||
- 绘制调用数量
|
||||
- 批处理数量
|
||||
- 三角形和顶点数量
|
||||
|
||||
**使用方法:**
|
||||
1. 将PerformanceProfiler组件添加到场景中的GameObject
|
||||
2. 在Inspector中配置监控选项
|
||||
3. 运行游戏查看实时性能数据
|
||||
|
||||
### 2. PerformanceOptimizer
|
||||
提供具体的优化建议:
|
||||
- 自动分析性能瓶颈
|
||||
- 生成优化建议报告
|
||||
- 提供自动优化选项
|
||||
|
||||
**使用方法:**
|
||||
1. 将PerformanceOptimizer组件添加到场景中
|
||||
2. 确保场景中有PerformanceProfiler组件
|
||||
3. 运行游戏查看优化建议
|
||||
|
||||
### 3. ObjectPool
|
||||
管理对象池,减少内存分配:
|
||||
- 预创建对象池
|
||||
- 复用对象而不是重新创建
|
||||
- 自动管理对象生命周期
|
||||
|
||||
**使用方法:**
|
||||
1. 将ObjectPool组件添加到场景中
|
||||
2. 在Inspector中配置需要池化的预制体
|
||||
3. 使用GetFromPool和ReturnToPool方法
|
||||
|
||||
## 具体优化建议
|
||||
|
||||
### 代码层面优化
|
||||
|
||||
#### 1. 缓存组件引用
|
||||
```csharp
|
||||
// 优化前
|
||||
void Update()
|
||||
{
|
||||
var manager = FindObjectOfType<GameManager>();
|
||||
manager.DoSomething();
|
||||
}
|
||||
|
||||
// 优化后
|
||||
private GameManager _gameManager;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_gameManager = FindObjectOfType<GameManager>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
_gameManager.DoSomething();
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 使用对象池
|
||||
```csharp
|
||||
// 优化前
|
||||
GameObject obj = Instantiate(prefab);
|
||||
Destroy(obj, 2f);
|
||||
|
||||
// 优化后
|
||||
GameObject obj = ObjectPool.Instance.GetFromPool("prefabTag");
|
||||
ObjectPool.Instance.ReturnToPool(obj, 2f);
|
||||
```
|
||||
|
||||
#### 3. 优化Update方法
|
||||
```csharp
|
||||
// 优化前
|
||||
void Update()
|
||||
{
|
||||
// 复杂的计算逻辑
|
||||
if (Time.frameCount % 30 == 0) // 每30帧执行一次
|
||||
{
|
||||
// 复杂计算
|
||||
}
|
||||
}
|
||||
|
||||
// 优化后
|
||||
void Start()
|
||||
{
|
||||
StartCoroutine(ComplexCalculationCoroutine());
|
||||
}
|
||||
|
||||
IEnumerator ComplexCalculationCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// 复杂计算
|
||||
yield return new WaitForSeconds(0.5f); // 每0.5秒执行一次
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 渲染优化
|
||||
|
||||
#### 1. 减少绘制调用
|
||||
- 合并使用相同材质的对象
|
||||
- 使用静态批处理
|
||||
- 减少透明物体的使用
|
||||
|
||||
#### 2. 优化UI
|
||||
- 减少Canvas数量
|
||||
- 使用Canvas Group管理UI层级
|
||||
- 避免频繁的UI更新
|
||||
|
||||
#### 3. 纹理优化
|
||||
- 使用适当的纹理压缩格式
|
||||
- 减少纹理大小
|
||||
- 使用纹理图集
|
||||
|
||||
### 内存优化
|
||||
|
||||
#### 1. 资源管理
|
||||
- 及时释放不需要的资源
|
||||
- 使用Resources.UnloadUnusedAssets()
|
||||
- 避免内存泄漏
|
||||
|
||||
#### 2. 对象生命周期管理
|
||||
- 使用对象池管理频繁创建的对象
|
||||
- 及时销毁不需要的对象
|
||||
- 避免在Update中创建临时对象
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 第一步:添加性能监控
|
||||
1. 在场景中添加PerformanceProfiler组件
|
||||
2. 运行游戏,观察性能指标
|
||||
3. 记录性能瓶颈
|
||||
|
||||
### 第二步:应用代码优化
|
||||
1. 替换FindObjectOfType调用为缓存引用
|
||||
2. 优化Update方法中的复杂逻辑
|
||||
3. 使用对象池管理频繁创建的对象
|
||||
|
||||
### 第三步:渲染优化
|
||||
1. 检查绘制调用数量
|
||||
2. 优化材质和纹理
|
||||
3. 调整UI结构
|
||||
|
||||
### 第四步:测试和验证
|
||||
1. 运行性能测试
|
||||
2. 对比优化前后的性能数据
|
||||
3. 确保功能正常
|
||||
|
||||
## 监控指标
|
||||
|
||||
### 目标性能指标
|
||||
- FPS: 60+ (移动设备30+)
|
||||
- 内存使用: <200MB
|
||||
- 绘制调用: <100
|
||||
- 批处理: <50
|
||||
|
||||
### 警告阈值
|
||||
- FPS < 30
|
||||
- 内存使用 > 200MB
|
||||
- 绘制调用 > 100
|
||||
- 批处理 > 50
|
||||
|
||||
## 常见问题解决
|
||||
|
||||
### Q: FPS突然下降怎么办?
|
||||
A:
|
||||
1. 检查是否有大量对象同时创建
|
||||
2. 查看Profiler中的CPU使用情况
|
||||
3. 检查Update方法中的复杂计算
|
||||
|
||||
### Q: 内存使用过高怎么办?
|
||||
A:
|
||||
1. 检查是否有内存泄漏
|
||||
2. 使用对象池减少内存分配
|
||||
3. 及时释放不需要的资源
|
||||
|
||||
### Q: 绘制调用过多怎么办?
|
||||
A:
|
||||
1. 合并使用相同材质的对象
|
||||
2. 使用静态批处理
|
||||
3. 减少透明物体的使用
|
||||
|
||||
## 总结
|
||||
|
||||
通过实施这些优化措施,您的游戏性能应该会有显著提升。建议按照以下顺序进行优化:
|
||||
|
||||
1. 首先添加性能监控工具
|
||||
2. 识别具体的性能瓶颈
|
||||
3. 应用代码层面的优化
|
||||
4. 进行渲染优化
|
||||
5. 测试和验证优化效果
|
||||
|
||||
记住,性能优化是一个持续的过程,需要不断监控和改进。
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5866f6b185f1cc4a975bed9bd50cc3c
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace AibisDream.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 性能优化建议工具
|
||||
/// </summary>
|
||||
public class PerformanceOptimizer : MonoBehaviour
|
||||
{
|
||||
[Header("优化建议设置")]
|
||||
public bool enableOptimizationTips = true;
|
||||
public bool autoApplyOptimizations = false;
|
||||
|
||||
[Header("性能阈值")]
|
||||
public float targetFPS = 60f;
|
||||
public float maxMemoryUsage = 200f; // MB
|
||||
public int maxDrawCalls = 100;
|
||||
public int maxBatches = 50;
|
||||
|
||||
private PerformanceProfiler profiler;
|
||||
private List<string> optimizationTips = new List<string>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
profiler = FindObjectOfType<PerformanceProfiler>();
|
||||
if (profiler == null)
|
||||
{
|
||||
Debug.LogWarning("未找到PerformanceProfiler组件,请先添加性能分析器");
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableOptimizationTips)
|
||||
{
|
||||
StartCoroutine(OptimizationMonitor());
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator OptimizationMonitor()
|
||||
{
|
||||
while (enableOptimizationTips)
|
||||
{
|
||||
AnalyzePerformance();
|
||||
yield return new WaitForSeconds(5f); // 每5秒分析一次
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzePerformance()
|
||||
{
|
||||
optimizationTips.Clear();
|
||||
|
||||
if (profiler == null) return;
|
||||
|
||||
float fps = profiler.GetFPS();
|
||||
float memory = profiler.GetMemoryUsage();
|
||||
int drawCalls = profiler.GetDrawCalls();
|
||||
int batches = profiler.GetBatches();
|
||||
|
||||
// FPS优化建议
|
||||
if (fps < targetFPS)
|
||||
{
|
||||
optimizationTips.Add("FPS优化建议:");
|
||||
optimizationTips.Add("• 减少Update方法中的复杂计算");
|
||||
optimizationTips.Add("• 使用对象池减少Instantiate/Destroy调用");
|
||||
optimizationTips.Add("• 优化FindObjectOfType调用,缓存组件引用");
|
||||
optimizationTips.Add("• 减少每帧的GetComponent调用");
|
||||
optimizationTips.Add("• 使用协程替代Update中的复杂逻辑");
|
||||
}
|
||||
|
||||
// 内存优化建议
|
||||
if (memory > maxMemoryUsage)
|
||||
{
|
||||
optimizationTips.Add("内存优化建议:");
|
||||
optimizationTips.Add("• 及时释放不需要的资源");
|
||||
optimizationTips.Add("• 使用对象池管理频繁创建的对象");
|
||||
optimizationTips.Add("• 检查是否有内存泄漏");
|
||||
optimizationTips.Add("• 优化纹理压缩设置");
|
||||
optimizationTips.Add("• 减少不必要的MonoBehaviour组件");
|
||||
}
|
||||
|
||||
// 渲染优化建议
|
||||
if (drawCalls > maxDrawCalls || batches > maxBatches)
|
||||
{
|
||||
optimizationTips.Add("渲染优化建议:");
|
||||
optimizationTips.Add("• 合并使用相同材质的对象");
|
||||
optimizationTips.Add("• 使用静态批处理(Static Batching)");
|
||||
optimizationTips.Add("• 减少透明物体的使用");
|
||||
optimizationTips.Add("• 优化UI Canvas的层级结构");
|
||||
optimizationTips.Add("• 使用LOD系统减少远处物体的细节");
|
||||
}
|
||||
|
||||
// 通用优化建议
|
||||
optimizationTips.Add("通用优化建议:");
|
||||
optimizationTips.Add("• 使用Profiler分析具体瓶颈");
|
||||
optimizationTips.Add("• 启用Unity的Burst编译");
|
||||
optimizationTips.Add("• 使用Job System进行多线程计算");
|
||||
optimizationTips.Add("• 优化协程和异步操作");
|
||||
optimizationTips.Add("• 减少字符串连接操作,使用StringBuilder");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取优化建议
|
||||
/// </summary>
|
||||
public List<string> GetOptimizationTips()
|
||||
{
|
||||
return new List<string>(optimizationTips);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打印优化建议到控制台
|
||||
/// </summary>
|
||||
public void PrintOptimizationTips()
|
||||
{
|
||||
if (optimizationTips.Count == 0)
|
||||
{
|
||||
Debug.Log("当前性能表现良好,无需优化");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("=== 性能优化建议 ===");
|
||||
foreach (string tip in optimizationTips)
|
||||
{
|
||||
Debug.Log(tip);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用自动优化
|
||||
/// </summary>
|
||||
public void ApplyAutoOptimizations()
|
||||
{
|
||||
if (!autoApplyOptimizations) return;
|
||||
|
||||
Debug.Log("应用自动优化...");
|
||||
|
||||
// 自动优化示例
|
||||
OptimizeUpdateMethods();
|
||||
OptimizeFindObjectOfTypeCalls();
|
||||
OptimizeGetComponentCalls();
|
||||
}
|
||||
|
||||
private void OptimizeUpdateMethods()
|
||||
{
|
||||
// 查找所有Update方法并给出建议
|
||||
MonoBehaviour[] allMonoBehaviours = FindObjectsOfType<MonoBehaviour>();
|
||||
|
||||
foreach (MonoBehaviour mb in allMonoBehaviours)
|
||||
{
|
||||
if (mb.GetType().GetMethod("Update") != null)
|
||||
{
|
||||
// 这里可以添加具体的优化逻辑
|
||||
Debug.Log($"发现Update方法: {mb.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OptimizeFindObjectOfTypeCalls()
|
||||
{
|
||||
// 查找所有FindObjectOfType调用
|
||||
Debug.Log("建议将FindObjectOfType调用移到Start/Awake中缓存结果");
|
||||
}
|
||||
|
||||
private void OptimizeGetComponentCalls()
|
||||
{
|
||||
// 查找所有GetComponent调用
|
||||
Debug.Log("建议缓存GetComponent的结果,避免重复调用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建性能优化报告
|
||||
/// </summary>
|
||||
public void CreatePerformanceReport()
|
||||
{
|
||||
if (profiler == null) return;
|
||||
|
||||
string report = "=== 性能报告 ===\n";
|
||||
report += $"FPS: {profiler.GetFPS():F1}\n";
|
||||
report += $"内存使用: {profiler.GetMemoryUsage():F1}MB\n";
|
||||
report += $"绘制调用: {profiler.GetDrawCalls()}\n";
|
||||
report += $"批处理: {profiler.GetBatches()}\n";
|
||||
report += "\n优化建议:\n";
|
||||
|
||||
foreach (string tip in optimizationTips)
|
||||
{
|
||||
report += tip + "\n";
|
||||
}
|
||||
|
||||
Debug.Log(report);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa69d1942a5d5414c92100b235a71e21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,549 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Rendering;
|
||||
using Unity.Profiling;
|
||||
using System.Diagnostics;
|
||||
using Debug = UnityEngine.Debug;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace AibisDream.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 性能分析工具,用于监控游戏性能
|
||||
/// </summary>
|
||||
public class PerformanceProfiler : MonoBehaviour
|
||||
{
|
||||
[Header("性能监控设置")]
|
||||
public bool enableProfiling = true;
|
||||
public bool showFPS = true;
|
||||
public bool showMemory = true;
|
||||
public bool showDrawCalls = true;
|
||||
public bool showBatches = true;
|
||||
public bool showTris = true;
|
||||
public bool showVerts = true;
|
||||
|
||||
[Header("数据记录设置")]
|
||||
public bool enableCsvLogging = false;
|
||||
public float logInterval = 0.5f; // 秒
|
||||
public string csvFileName = "performance_log";
|
||||
[Header("性能警告阈值")]
|
||||
public float fpsWarningThreshold = 30f;
|
||||
public float memoryWarningThreshold = 100f; // MB
|
||||
public int drawCallWarningThreshold = 100;
|
||||
public int batchWarningThreshold = 50;
|
||||
|
||||
[Header("显示设置")]
|
||||
public Color normalColor = Color.white;
|
||||
public Color warningColor = Color.yellow;
|
||||
public Color errorColor = Color.red;
|
||||
public int fontSize = 14;
|
||||
|
||||
[Header("CPU/GPU 时间分析")]
|
||||
public bool showCPUTime = true;
|
||||
public bool showGPUTime = true;
|
||||
public float cpuTimeWarningThreshold = 16.67f; // 60fps对应的帧时间
|
||||
public float gpuTimeWarningThreshold = 16.67f;
|
||||
|
||||
[Header("GC监控")]
|
||||
public bool showGCStats = true;
|
||||
public int gcWarningThreshold = 5; // 每秒GC次数警告阈值
|
||||
|
||||
[Header("资源监控")]
|
||||
public bool showResourceStats = true;
|
||||
public long textureMemoryWarningThreshold = 1024; // MB
|
||||
public long meshMemoryWarningThreshold = 512; // MB
|
||||
|
||||
[Header("系统性能分析")]
|
||||
public bool enableSystemProfiling = true;
|
||||
public float systemTimeWarningThreshold = 8f; // ms
|
||||
|
||||
private float deltaTime = 0.0f;
|
||||
private float cpuFrameTime;
|
||||
private float gpuFrameTime;
|
||||
private long lastGCCount;
|
||||
private float gcTimeAccumulator;
|
||||
private int gcCountInLastSecond;
|
||||
|
||||
private int loadedTextureCount;
|
||||
private int loadedMeshCount;
|
||||
private long totalTextureMemory;
|
||||
private long totalMeshMemory;
|
||||
|
||||
private Dictionary<string, CustomProfiler> systemProfilers = new Dictionary<string, CustomProfiler>();
|
||||
|
||||
public class CustomProfiler
|
||||
{
|
||||
public string name;
|
||||
public float totalTime;
|
||||
public float maxTime;
|
||||
public int sampleCount;
|
||||
public Stopwatch stopwatch;
|
||||
|
||||
public float AverageTime => sampleCount > 0 ? totalTime / sampleCount : 0;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
totalTime = 0;
|
||||
maxTime = 0;
|
||||
sampleCount = 0;
|
||||
stopwatch = new Stopwatch();
|
||||
}
|
||||
}
|
||||
private float fps = 0.0f;
|
||||
private float memoryUsage = 0.0f;
|
||||
private int drawCalls = 0;
|
||||
private int batches = 0;
|
||||
private int triangles = 0;
|
||||
private int vertices = 0;
|
||||
|
||||
private GUIStyle style;
|
||||
private List<string> performanceWarnings = new List<string>();
|
||||
|
||||
// CSV 日志
|
||||
private StreamWriter csvWriter;
|
||||
private float nextLogTime = 0f;
|
||||
private string logFilePath;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (!enableProfiling) return;
|
||||
|
||||
// 创建GUI样式
|
||||
style = new GUIStyle();
|
||||
style.fontSize = fontSize;
|
||||
style.normal.textColor = normalColor;
|
||||
|
||||
// 启动性能监控协程
|
||||
StartCoroutine(PerformanceMonitor());
|
||||
|
||||
// 初始化CSV日志
|
||||
if (enableCsvLogging)
|
||||
{
|
||||
InitCsvLogging();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!enableProfiling) return;
|
||||
|
||||
// 计算FPS
|
||||
deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
|
||||
fps = 1.0f / deltaTime;
|
||||
|
||||
// 获取渲染统计信息
|
||||
#if UNITY_EDITOR
|
||||
drawCalls = UnityEditor.UnityStats.drawCalls;
|
||||
batches = UnityEditor.UnityStats.batches;
|
||||
triangles = UnityEditor.UnityStats.triangles;
|
||||
vertices = UnityEditor.UnityStats.vertices;
|
||||
#else
|
||||
drawCalls = 0;
|
||||
batches = 0;
|
||||
triangles = 0;
|
||||
vertices = 0;
|
||||
#endif
|
||||
|
||||
// 获取内存使用情况
|
||||
memoryUsage = Profiler.GetTotalAllocatedMemoryLong() / (1024f * 1024f); // 转换为MB
|
||||
|
||||
// CPU和GPU时间
|
||||
cpuFrameTime = Time.unscaledDeltaTime * 1000f; // 转换为毫秒
|
||||
if (FrameTimingManager.IsFeatureEnabled())
|
||||
{
|
||||
var frameData = new FrameTiming[1];
|
||||
FrameTimingManager.GetLatestTimings(1, frameData);
|
||||
if (frameData.Length > 0)
|
||||
{
|
||||
gpuFrameTime = (float)frameData[0].gpuFrameTime;
|
||||
}
|
||||
}
|
||||
|
||||
// GC监控
|
||||
if (System.GC.CollectionCount(0) > lastGCCount)
|
||||
{
|
||||
gcCountInLastSecond++;
|
||||
lastGCCount = System.GC.CollectionCount(0);
|
||||
}
|
||||
|
||||
// 资源监控
|
||||
if (showResourceStats)
|
||||
{
|
||||
var textures = Resources.FindObjectsOfTypeAll<Texture>();
|
||||
var meshes = Resources.FindObjectsOfTypeAll<Mesh>();
|
||||
|
||||
loadedTextureCount = textures.Length;
|
||||
loadedMeshCount = meshes.Length;
|
||||
|
||||
totalTextureMemory = 0;
|
||||
foreach (var tex in textures)
|
||||
{
|
||||
if (tex != null)
|
||||
totalTextureMemory += Profiler.GetRuntimeMemorySizeLong(tex);
|
||||
}
|
||||
|
||||
totalMeshMemory = 0;
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
if (mesh != null)
|
||||
totalMeshMemory += Profiler.GetRuntimeMemorySizeLong(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
// 按间隔写入CSV
|
||||
if (enableCsvLogging && csvWriter != null && Time.unscaledTime >= nextLogTime)
|
||||
{
|
||||
WriteCsvRow();
|
||||
nextLogTime = Time.unscaledTime + Mathf.Max(0.05f, logInterval);
|
||||
gcCountInLastSecond = 0; // 重置GC计数器
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator PerformanceMonitor()
|
||||
{
|
||||
while (enableProfiling)
|
||||
{
|
||||
performanceWarnings.Clear();
|
||||
|
||||
// 检查FPS
|
||||
if (fps < fpsWarningThreshold)
|
||||
{
|
||||
performanceWarnings.Add($"FPS过低: {fps:F1}");
|
||||
}
|
||||
|
||||
// 检查内存使用
|
||||
if (memoryUsage > memoryWarningThreshold)
|
||||
{
|
||||
performanceWarnings.Add($"内存使用过高: {memoryUsage:F1}MB");
|
||||
}
|
||||
|
||||
// 检查绘制调用
|
||||
if (drawCalls > drawCallWarningThreshold)
|
||||
{
|
||||
performanceWarnings.Add($"绘制调用过多: {drawCalls}");
|
||||
}
|
||||
|
||||
// 检查批处理
|
||||
if (batches > batchWarningThreshold)
|
||||
{
|
||||
performanceWarnings.Add($"批处理过多: {batches}");
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!enableProfiling) return;
|
||||
|
||||
float yPos = 10f;
|
||||
float lineHeight = fontSize + 5f;
|
||||
|
||||
// 显示FPS
|
||||
if (showFPS)
|
||||
{
|
||||
Color fpsColor = fps < fpsWarningThreshold ? errorColor : normalColor;
|
||||
style.normal.textColor = fpsColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"FPS: {fps:F1}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示内存使用
|
||||
if (showMemory)
|
||||
{
|
||||
Color memColor = memoryUsage > memoryWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = memColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"内存: {memoryUsage:F1}MB", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示绘制调用
|
||||
if (showDrawCalls)
|
||||
{
|
||||
Color drawColor = drawCalls > drawCallWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = drawColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"绘制调用: {drawCalls}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示批处理
|
||||
if (showBatches)
|
||||
{
|
||||
Color batchColor = batches > batchWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = batchColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"批处理: {batches}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示三角形数量
|
||||
if (showTris)
|
||||
{
|
||||
style.normal.textColor = normalColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"三角形: {triangles:N0}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示顶点数量
|
||||
if (showVerts)
|
||||
{
|
||||
style.normal.textColor = normalColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"顶点: {vertices:N0}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示CPU时间
|
||||
if (showCPUTime)
|
||||
{
|
||||
Color timeColor = cpuFrameTime > cpuTimeWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = timeColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"CPU时间: {cpuFrameTime:F2}ms", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示GPU时间
|
||||
if (showGPUTime && FrameTimingManager.IsFeatureEnabled())
|
||||
{
|
||||
Color timeColor = gpuFrameTime > gpuTimeWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = timeColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"GPU时间: {gpuFrameTime:F2}ms", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示GC信息
|
||||
if (showGCStats)
|
||||
{
|
||||
Color gcColor = gcCountInLastSecond > gcWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = gcColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), $"GC次数/秒: {gcCountInLastSecond}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示资源信息
|
||||
if (showResourceStats)
|
||||
{
|
||||
yPos += 10f;
|
||||
style.normal.textColor = normalColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), "资源统计:", style);
|
||||
yPos += lineHeight;
|
||||
|
||||
Color texMemColor = (totalTextureMemory / (1024f * 1024f)) > textureMemoryWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = texMemColor;
|
||||
GUI.Label(new Rect(20, yPos, 300, lineHeight),
|
||||
$"贴图: {loadedTextureCount}个 ({totalTextureMemory / (1024f * 1024f):F1}MB)", style);
|
||||
yPos += lineHeight;
|
||||
|
||||
Color meshMemColor = (totalMeshMemory / (1024f * 1024f)) > meshMemoryWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = meshMemColor;
|
||||
GUI.Label(new Rect(20, yPos, 300, lineHeight),
|
||||
$"网格: {loadedMeshCount}个 ({totalMeshMemory / (1024f * 1024f):F1}MB)", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
// 显示系统性能分析
|
||||
if (enableSystemProfiling && systemProfilers.Count > 0)
|
||||
{
|
||||
yPos += 10f;
|
||||
style.normal.textColor = normalColor;
|
||||
GUI.Label(new Rect(10, yPos, 200, lineHeight), "系统性能:", style);
|
||||
yPos += lineHeight;
|
||||
|
||||
foreach (var profiler in systemProfilers.Values)
|
||||
{
|
||||
if (profiler.sampleCount > 0)
|
||||
{
|
||||
Color timeColor = profiler.AverageTime > systemTimeWarningThreshold ? warningColor : normalColor;
|
||||
style.normal.textColor = timeColor;
|
||||
GUI.Label(new Rect(20, yPos, 400, lineHeight),
|
||||
$"{profiler.name}: {profiler.AverageTime:F2}ms (最大: {profiler.maxTime:F2}ms)", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显示性能警告
|
||||
if (performanceWarnings.Count > 0)
|
||||
{
|
||||
yPos += 10f;
|
||||
style.normal.textColor = errorColor;
|
||||
GUI.Label(new Rect(10, yPos, 400, lineHeight), "性能警告:", style);
|
||||
yPos += lineHeight;
|
||||
|
||||
foreach (string warning in performanceWarnings)
|
||||
{
|
||||
GUI.Label(new Rect(20, yPos, 400, lineHeight), $"• {warning}", style);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前FPS
|
||||
/// </summary>
|
||||
public float GetFPS()
|
||||
{
|
||||
return fps;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内存使用量(MB)
|
||||
/// </summary>
|
||||
public float GetMemoryUsage()
|
||||
{
|
||||
return memoryUsage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取绘制调用数量
|
||||
/// </summary>
|
||||
public int GetDrawCalls()
|
||||
{
|
||||
return drawCalls;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取批处理数量
|
||||
/// </summary>
|
||||
public int GetBatches()
|
||||
{
|
||||
return batches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录性能数据到日志
|
||||
/// </summary>
|
||||
public void LogPerformanceData()
|
||||
{
|
||||
Debug.Log($"[性能数据] FPS: {fps:F1}, 内存: {memoryUsage:F1}MB, 绘制调用: {drawCalls}, 批处理: {batches}");
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
CloseCsv();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CloseCsv();
|
||||
}
|
||||
|
||||
private void InitCsvLogging()
|
||||
{
|
||||
try
|
||||
{
|
||||
var directory = Application.persistentDataPath;
|
||||
logFilePath = Path.Combine(directory, $"perf_{System.DateTime.Now:yyyyMMdd_HHmmss}.csv");
|
||||
csvWriter = new StreamWriter(new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite));
|
||||
csvWriter.WriteLine("time,fps,memoryMB,drawCalls,batches,triangles,vertices,cpuTime,gpuTime,gcCount," +
|
||||
"textureCount,meshCount,textureMemoryMB,meshMemoryMB");
|
||||
csvWriter.Flush();
|
||||
nextLogTime = Time.unscaledTime + Mathf.Max(0.05f, logInterval);
|
||||
Debug.Log($"性能CSV日志: {logFilePath}");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"初始化CSV日志失败: {e.Message}");
|
||||
enableCsvLogging = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCsvRow()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (csvWriter == null) return;
|
||||
csvWriter.WriteLine($"{Time.unscaledTime:F3},{fps:F2},{memoryUsage:F1},{drawCalls},{batches},{triangles},{vertices}," +
|
||||
$"{cpuFrameTime:F2},{gpuFrameTime:F2},{gcCountInLastSecond}," +
|
||||
$"{loadedTextureCount},{loadedMeshCount},{totalTextureMemory/1024/1024:F1},{totalMeshMemory/1024/1024:F1}");
|
||||
csvWriter.Flush();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"写入CSV失败: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseCsv()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (csvWriter != null)
|
||||
{
|
||||
csvWriter.Flush();
|
||||
csvWriter.Close();
|
||||
csvWriter.Dispose();
|
||||
csvWriter = null;
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
public string GetLogFilePath()
|
||||
{
|
||||
return logFilePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始系统性能采样
|
||||
/// </summary>
|
||||
public void BeginSample(string systemName)
|
||||
{
|
||||
if (!enableSystemProfiling) return;
|
||||
if (!systemProfilers.ContainsKey(systemName))
|
||||
{
|
||||
systemProfilers[systemName] = new CustomProfiler
|
||||
{
|
||||
name = systemName,
|
||||
stopwatch = new Stopwatch()
|
||||
};
|
||||
}
|
||||
systemProfilers[systemName].stopwatch.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束系统性能采样
|
||||
/// </summary>
|
||||
public void EndSample(string systemName)
|
||||
{
|
||||
if (!enableSystemProfiling) return;
|
||||
if (systemProfilers.TryGetValue(systemName, out var profiler))
|
||||
{
|
||||
profiler.stopwatch.Stop();
|
||||
float time = profiler.stopwatch.ElapsedTicks / (float)System.TimeSpan.TicksPerMillisecond;
|
||||
profiler.totalTime += time;
|
||||
profiler.maxTime = Mathf.Max(profiler.maxTime, time);
|
||||
profiler.sampleCount++;
|
||||
profiler.stopwatch.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置所有系统性能分析器
|
||||
/// </summary>
|
||||
public void ResetAllProfilers()
|
||||
{
|
||||
foreach (var profiler in systemProfilers.Values)
|
||||
{
|
||||
profiler.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统性能数据
|
||||
/// </summary>
|
||||
public Dictionary<string, (float averageTime, float maxTime, int sampleCount)> GetSystemProfilingData()
|
||||
{
|
||||
var result = new Dictionary<string, (float, float, int)>();
|
||||
foreach (var kvp in systemProfilers)
|
||||
{
|
||||
var profiler = kvp.Value;
|
||||
result[kvp.Key] = (profiler.AverageTime, profiler.maxTime, profiler.sampleCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cc8ba26ea081f143bce9da130c6992a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user