添加缺失插件
This commit is contained in:
8
Assets/SoraTools/SceneAnalyzer.meta
Normal file
8
Assets/SoraTools/SceneAnalyzer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7723cab0703e3a74b9e989289246b5c4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/SoraTools/SceneAnalyzer/Editor.meta
Normal file
8
Assets/SoraTools/SceneAnalyzer/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f45866dcd12dad3429179152a3985796
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
639
Assets/SoraTools/SceneAnalyzer/Editor/SceneAnalyzer.cs
Normal file
639
Assets/SoraTools/SceneAnalyzer/Editor/SceneAnalyzer.cs
Normal file
@@ -0,0 +1,639 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System.Linq;
|
||||
using UnityEngine.Profiling;
|
||||
using System.Text;
|
||||
|
||||
namespace SoraTools
|
||||
{
|
||||
public class SceneAnalyzer : EditorWindow
|
||||
{
|
||||
private enum TargetDevice
|
||||
{
|
||||
Android_8G,
|
||||
Android_12G
|
||||
}
|
||||
|
||||
private TargetDevice currentTarget = TargetDevice.Android_8G;
|
||||
private Vector2 scrollPos;
|
||||
|
||||
// 分析结果缓存
|
||||
private bool hasAnalyzed = false;
|
||||
private AnalysisData currentData;
|
||||
|
||||
// GUI Styles
|
||||
private GUIStyle titleStyle;
|
||||
private GUIStyle headerStyle;
|
||||
private GUIStyle boxStyle;
|
||||
private GUIStyle resultGoodStyle;
|
||||
private GUIStyle resultWarnStyle;
|
||||
private GUIStyle resultBadStyle;
|
||||
private GUIStyle labelStyle;
|
||||
private GUIStyle linkStyle;
|
||||
|
||||
[MenuItem("SoraTools/Scene Analyzer %&r")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<SceneAnalyzer>("Scene Analyzer");
|
||||
window.minSize = new Vector2(400, 600);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// 初始化样式
|
||||
InitStyles();
|
||||
}
|
||||
|
||||
private void InitStyles()
|
||||
{
|
||||
titleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 18,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
margin = new RectOffset(0, 0, 10, 10)
|
||||
};
|
||||
|
||||
headerStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 14,
|
||||
margin = new RectOffset(0, 0, 10, 5)
|
||||
};
|
||||
|
||||
boxStyle = new GUIStyle(EditorStyles.helpBox)
|
||||
{
|
||||
padding = new RectOffset(10, 10, 10, 10),
|
||||
margin = new RectOffset(5, 5, 5, 5)
|
||||
};
|
||||
|
||||
labelStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
fontSize = 12,
|
||||
richText = true
|
||||
};
|
||||
|
||||
resultGoodStyle = new GUIStyle(EditorStyles.label) { normal = { textColor = new Color(0.2f, 0.8f, 0.2f) }, fontStyle = FontStyle.Bold };
|
||||
resultWarnStyle = new GUIStyle(EditorStyles.label) { normal = { textColor = new Color(0.9f, 0.7f, 0.0f) }, fontStyle = FontStyle.Bold };
|
||||
resultBadStyle = new GUIStyle(EditorStyles.label) { normal = { textColor = new Color(0.9f, 0.3f, 0.3f) }, fontStyle = FontStyle.Bold };
|
||||
|
||||
linkStyle = new GUIStyle(EditorStyles.label) {
|
||||
normal = { textColor = new Color(0.3f, 0.6f, 1.0f) },
|
||||
hover = { textColor = new Color(0.4f, 0.7f, 1.0f) },
|
||||
fontStyle = FontStyle.Italic
|
||||
};
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (titleStyle == null) InitStyles();
|
||||
|
||||
EditorGUILayout.LabelField("场景性能分析工具", titleStyle);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 设置区域
|
||||
EditorGUILayout.BeginVertical(boxStyle);
|
||||
EditorGUILayout.LabelField("目标设备设置", headerStyle);
|
||||
currentTarget = (TargetDevice)EditorGUILayout.EnumPopup("目标设备内存", currentTarget);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button("开始分析当前场景", GUILayout.Height(40)))
|
||||
{
|
||||
AnalyzeScene();
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
if (!hasAnalyzed || currentData == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("请点击上方按钮开始分析当前场景。", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
// 结果展示区域
|
||||
EditorGUILayout.Space();
|
||||
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
|
||||
|
||||
DrawSection("场景概览", () =>
|
||||
{
|
||||
DrawStatItem("GameObjects 总数", currentData.objectCount,
|
||||
GetThreshold(MetricType.ObjectCount).warning,
|
||||
GetThreshold(MetricType.ObjectCount).limit);
|
||||
|
||||
DrawStatItem("总顶点数 (Vertices)", currentData.totalVertices,
|
||||
GetThreshold(MetricType.Vertices).warning,
|
||||
GetThreshold(MetricType.Vertices).limit, FormatNumber);
|
||||
|
||||
DrawStatItem("总面数 (Triangles)", currentData.totalTriangles,
|
||||
GetThreshold(MetricType.Triangles).warning,
|
||||
GetThreshold(MetricType.Triangles).limit, FormatNumber);
|
||||
|
||||
DrawStatItem("非 Static 物体", currentData.nonStaticCount,
|
||||
500, 1000, null,
|
||||
"点击选中非Static物体", currentData.nonStaticObjects);
|
||||
|
||||
DrawStatItem("Missing Scripts", currentData.missingScriptCount,
|
||||
0, 0, null,
|
||||
"点击选中Missing Script物体", currentData.missingScriptObjects);
|
||||
});
|
||||
|
||||
DrawSection("灯光与渲染 (Lighting & Rendering)", () =>
|
||||
{
|
||||
DrawStatItem("实时光源 (Realtime Lights)", currentData.realtimeLightCount,
|
||||
GetThreshold(MetricType.RealtimeLights).warning,
|
||||
GetThreshold(MetricType.RealtimeLights).limit, null,
|
||||
"点击选中实时光源", currentData.realtimeLights);
|
||||
|
||||
DrawStatItem("阴影投射光源 (Shadow Casters)", currentData.shadowCastingLights,
|
||||
GetThreshold(MetricType.ShadowLights).warning,
|
||||
GetThreshold(MetricType.ShadowLights).limit, null,
|
||||
"点击选中阴影光源", currentData.shadowLights);
|
||||
|
||||
DrawStatItem("总光源数量 (All Lights)", currentData.totalLightCount,
|
||||
GetThreshold(MetricType.TotalLights).warning,
|
||||
GetThreshold(MetricType.TotalLights).limit);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("是否开启 Fog", GUILayout.Width(200));
|
||||
EditorGUILayout.LabelField(currentData.hasFog ? "开启 (可能有性能消耗)" : "关闭",
|
||||
currentData.hasFog ? resultWarnStyle : resultGoodStyle);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
DrawStatItem("半透明物体 (Overdraw风险)", currentData.transparentObjectCount,
|
||||
GetThreshold(MetricType.TransparentObjects).warning,
|
||||
GetThreshold(MetricType.TransparentObjects).limit, null,
|
||||
"点击选中半透明物体", currentData.transparentObjects);
|
||||
});
|
||||
|
||||
DrawSection("动画与特效 (Animation & FX)", () =>
|
||||
{
|
||||
DrawStatItem("蒙皮网格 (Skinned Mesh)", currentData.skinnedMeshCount,
|
||||
GetThreshold(MetricType.SkinnedMesh).warning,
|
||||
GetThreshold(MetricType.SkinnedMesh).limit, null,
|
||||
"点击选中蒙皮网格", currentData.skinnedMeshes);
|
||||
|
||||
DrawStatItem("粒子系统 (Particle Systems)", currentData.particleSystemCount,
|
||||
GetThreshold(MetricType.ParticleSystems).warning,
|
||||
GetThreshold(MetricType.ParticleSystems).limit, null,
|
||||
"点击选中粒子系统", currentData.particleSystems);
|
||||
});
|
||||
|
||||
DrawSection("物理 (Physics)", () =>
|
||||
{
|
||||
DrawStatItem("复杂 MeshCollider", currentData.complexMeshColliderCount,
|
||||
0, 10, null,
|
||||
"点击选中复杂碰撞体", currentData.complexMeshColliders);
|
||||
});
|
||||
|
||||
DrawSection("内存占用 (估算)", () =>
|
||||
{
|
||||
DrawStatItem("纹理内存 (Texture)", currentData.textureMemory,
|
||||
GetThreshold(MetricType.TextureMem).warning,
|
||||
GetThreshold(MetricType.TextureMem).limit, FormatBytes,
|
||||
"查看 Top 10 纹理", null, () => ShowTopResourcesWindow("Top 10 Textures", currentData.topTextures));
|
||||
|
||||
DrawStatItem("网格内存 (Mesh)", currentData.meshMemory,
|
||||
GetThreshold(MetricType.MeshMem).warning,
|
||||
GetThreshold(MetricType.MeshMem).limit, FormatBytes,
|
||||
"查看 Top 10 网格", null, () => ShowTopResourcesWindow("Top 10 Meshes", currentData.topMeshes));
|
||||
|
||||
DrawStatItem("音频内存 (Audio)", currentData.audioMemory,
|
||||
GetThreshold(MetricType.AudioMem).warning,
|
||||
GetThreshold(MetricType.AudioMem).limit, FormatBytes);
|
||||
});
|
||||
|
||||
DrawSection("资源详情", () =>
|
||||
{
|
||||
DrawStatItem("独立纹理数量", currentData.uniqueTextureCount, 1000, 2000);
|
||||
DrawStatItem("独立材质数量", currentData.uniqueMaterialCount, 200, 500);
|
||||
DrawStatItem("独立网格数量", currentData.uniqueMeshCount, 500, 1000);
|
||||
});
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void AnalyzeScene()
|
||||
{
|
||||
EditorUtility.DisplayProgressBar("场景分析中", "正在收集场景对象...", 0.1f);
|
||||
|
||||
try
|
||||
{
|
||||
currentData = new AnalysisData();
|
||||
|
||||
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
|
||||
var allRenderers = new List<Renderer>();
|
||||
var allColliders = new List<Collider>();
|
||||
|
||||
// 递归收集所有对象
|
||||
int totalRoots = rootObjects.Length;
|
||||
for (int i = 0; i < totalRoots; i++)
|
||||
{
|
||||
var root = rootObjects[i];
|
||||
EditorUtility.DisplayProgressBar("场景分析中", $"正在分析对象: {root.name}", 0.1f + (float)i / totalRoots * 0.4f);
|
||||
|
||||
allRenderers.AddRange(root.GetComponentsInChildren<Renderer>(true));
|
||||
var colliders = root.GetComponentsInChildren<Collider>(true);
|
||||
allColliders.AddRange(colliders);
|
||||
|
||||
var transforms = root.GetComponentsInChildren<Transform>(true);
|
||||
currentData.objectCount += transforms.Length;
|
||||
|
||||
// 检查 Static Batching 和 Missing Scripts
|
||||
foreach (var t in transforms)
|
||||
{
|
||||
if (!t.gameObject.isStatic)
|
||||
{
|
||||
currentData.nonStaticCount++;
|
||||
currentData.nonStaticObjects.Add(t.gameObject);
|
||||
}
|
||||
|
||||
// 检查 Missing Scripts
|
||||
var components = t.GetComponents<Component>();
|
||||
foreach (var c in components)
|
||||
{
|
||||
if (c == null)
|
||||
{
|
||||
currentData.missingScriptCount++;
|
||||
currentData.missingScriptObjects.Add(t.gameObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查复杂 MeshCollider
|
||||
foreach(var col in colliders)
|
||||
{
|
||||
if(col is MeshCollider mc && !mc.convex)
|
||||
{
|
||||
currentData.complexMeshColliderCount++;
|
||||
currentData.complexMeshColliders.Add(col.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.DisplayProgressBar("场景分析中", "正在分析网格与材质...", 0.6f);
|
||||
|
||||
// 分析网格
|
||||
HashSet<Mesh> uniqueMeshes = new HashSet<Mesh>();
|
||||
List<ResourceInfo> meshInfos = new List<ResourceInfo>();
|
||||
|
||||
foreach (var renderer in allRenderers)
|
||||
{
|
||||
Mesh mesh = null;
|
||||
if (renderer is MeshRenderer)
|
||||
{
|
||||
var filter = renderer.GetComponent<MeshFilter>();
|
||||
if (filter != null) mesh = filter.sharedMesh;
|
||||
}
|
||||
else if (renderer is SkinnedMeshRenderer smr)
|
||||
{
|
||||
mesh = smr.sharedMesh;
|
||||
currentData.skinnedMeshCount++;
|
||||
currentData.skinnedMeshes.Add(smr.gameObject);
|
||||
}
|
||||
|
||||
if (mesh != null)
|
||||
{
|
||||
currentData.totalVertices += mesh.vertexCount;
|
||||
currentData.totalTriangles += mesh.triangles.Length / 3;
|
||||
|
||||
if (uniqueMeshes.Add(mesh))
|
||||
{
|
||||
long mem = Profiler.GetRuntimeMemorySizeLong(mesh);
|
||||
currentData.meshMemory += mem;
|
||||
meshInfos.Add(new ResourceInfo { name = mesh.name, size = mem, obj = mesh });
|
||||
}
|
||||
}
|
||||
|
||||
// 检查半透明物体
|
||||
foreach(var mat in renderer.sharedMaterials)
|
||||
{
|
||||
if (mat != null && (mat.renderQueue >= 2500 || mat.FindPass("Transparent") >= 0 || mat.GetTag("RenderType", false) == "Transparent"))
|
||||
{
|
||||
currentData.transparentObjectCount++;
|
||||
currentData.transparentObjects.Add(renderer.gameObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentData.uniqueMeshCount = uniqueMeshes.Count;
|
||||
currentData.topMeshes = meshInfos.OrderByDescending(x => x.size).Take(10).ToList();
|
||||
|
||||
// 分析材质和纹理
|
||||
HashSet<Material> uniqueMaterials = new HashSet<Material>();
|
||||
HashSet<Texture> uniqueTextures = new HashSet<Texture>();
|
||||
List<ResourceInfo> textureInfos = new List<ResourceInfo>();
|
||||
|
||||
foreach (var renderer in allRenderers)
|
||||
{
|
||||
foreach (var mat in renderer.sharedMaterials)
|
||||
{
|
||||
if (mat != null && uniqueMaterials.Add(mat))
|
||||
{
|
||||
var shader = mat.shader;
|
||||
int propertyCount = ShaderUtil.GetPropertyCount(shader);
|
||||
for (int i = 0; i < propertyCount; i++)
|
||||
{
|
||||
if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
|
||||
{
|
||||
string propertyName = ShaderUtil.GetPropertyName(shader, i);
|
||||
Texture tex = mat.GetTexture(propertyName);
|
||||
if (tex != null)
|
||||
{
|
||||
uniqueTextures.Add(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentData.uniqueMaterialCount = uniqueMaterials.Count;
|
||||
currentData.uniqueTextureCount = uniqueTextures.Count;
|
||||
|
||||
foreach (var tex in uniqueTextures)
|
||||
{
|
||||
long mem = Profiler.GetRuntimeMemorySizeLong(tex);
|
||||
currentData.textureMemory += mem;
|
||||
textureInfos.Add(new ResourceInfo { name = tex.name, size = mem, obj = tex });
|
||||
}
|
||||
currentData.topTextures = textureInfos.OrderByDescending(x => x.size).Take(10).ToList();
|
||||
|
||||
EditorUtility.DisplayProgressBar("场景分析中", "正在分析音频与灯光...", 0.8f);
|
||||
|
||||
// 分析音频
|
||||
var audioSources = Resources.FindObjectsOfTypeAll<AudioSource>();
|
||||
HashSet<AudioClip> uniqueClips = new HashSet<AudioClip>();
|
||||
foreach(var root in rootObjects)
|
||||
{
|
||||
var sources = root.GetComponentsInChildren<AudioSource>(true);
|
||||
foreach(var source in sources)
|
||||
{
|
||||
if(source.clip != null) uniqueClips.Add(source.clip);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(var clip in uniqueClips)
|
||||
{
|
||||
currentData.audioMemory += Profiler.GetRuntimeMemorySizeLong(clip);
|
||||
}
|
||||
|
||||
// 分析灯光
|
||||
var lights = Object.FindObjectsOfType<Light>();
|
||||
currentData.totalLightCount = lights.Length;
|
||||
foreach (var light in lights)
|
||||
{
|
||||
if (light.lightmapBakeType == LightmapBakeType.Realtime || light.lightmapBakeType == LightmapBakeType.Mixed)
|
||||
{
|
||||
currentData.realtimeLightCount++;
|
||||
currentData.realtimeLights.Add(light.gameObject);
|
||||
}
|
||||
if (light.shadows != LightShadows.None)
|
||||
{
|
||||
currentData.shadowCastingLights++;
|
||||
currentData.shadowLights.Add(light.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 分析粒子系统
|
||||
var particles = Object.FindObjectsOfType<ParticleSystem>();
|
||||
currentData.particleSystemCount = particles.Length;
|
||||
foreach(var ps in particles)
|
||||
{
|
||||
currentData.particleSystems.Add(ps.gameObject);
|
||||
}
|
||||
|
||||
// 分析 RenderSettings
|
||||
currentData.hasFog = RenderSettings.fog;
|
||||
|
||||
hasAnalyzed = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowTopResourcesWindow(string title, List<ResourceInfo> resources)
|
||||
{
|
||||
TopResourcesWindow.Show(title, resources);
|
||||
}
|
||||
|
||||
private void DrawSection(string title, System.Action content)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(boxStyle);
|
||||
EditorGUILayout.LabelField(title, headerStyle);
|
||||
EditorGUILayout.Space(5);
|
||||
content();
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private void DrawStatItem(string label, long value, long warningThreshold, long limitThreshold,
|
||||
System.Func<long, string> formatter = null, string selectButtonText = null, List<GameObject> objectsToSelect = null, System.Action onDetailClick = null)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(200));
|
||||
|
||||
string displayValue = formatter != null ? formatter(value) : value.ToString();
|
||||
string suggestion = "";
|
||||
GUIStyle style = resultGoodStyle;
|
||||
|
||||
if (value > limitThreshold)
|
||||
{
|
||||
style = resultBadStyle;
|
||||
suggestion = $"(严重超标! 建议 < {formatter?.Invoke(limitThreshold) ?? limitThreshold.ToString()})";
|
||||
}
|
||||
else if (value > warningThreshold)
|
||||
{
|
||||
style = resultWarnStyle;
|
||||
suggestion = $"(偏高, 建议 < {formatter?.Invoke(warningThreshold) ?? warningThreshold.ToString()})";
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField(displayValue, style, GUILayout.Width(100));
|
||||
|
||||
// 按钮逻辑
|
||||
if (onDetailClick != null)
|
||||
{
|
||||
if (GUILayout.Button(selectButtonText ?? "Detail", GUILayout.Width(120)))
|
||||
{
|
||||
onDetailClick.Invoke();
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(selectButtonText) && objectsToSelect != null && objectsToSelect.Count > 0)
|
||||
{
|
||||
if (GUILayout.Button("Select", GUILayout.Width(60)))
|
||||
{
|
||||
Selection.objects = objectsToSelect.ToArray();
|
||||
EditorGUIUtility.PingObject(objectsToSelect[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(suggestion))
|
||||
{
|
||||
EditorGUILayout.LabelField(suggestion, labelStyle);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private string FormatNumber(long num)
|
||||
{
|
||||
if (num > 1000000) return (num / 1000000f).ToString("F1") + "M";
|
||||
if (num > 1000) return (num / 1000f).ToString("F1") + "K";
|
||||
return num.ToString();
|
||||
}
|
||||
|
||||
private string FormatBytes(long bytes)
|
||||
{
|
||||
if (bytes > 1024 * 1024 * 1024) return (bytes / (1024f * 1024f * 1024f)).ToString("F2") + " GB";
|
||||
if (bytes > 1024 * 1024) return (bytes / (1024f * 1024f)).ToString("F1") + " MB";
|
||||
if (bytes > 1024) return (bytes / 1024f).ToString("F1") + " KB";
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
private (long warning, long limit) GetThreshold(MetricType type)
|
||||
{
|
||||
bool is12G = currentTarget == TargetDevice.Android_12G;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MetricType.ObjectCount:
|
||||
return is12G ? (10000, 15000) : (5000, 10000);
|
||||
case MetricType.Vertices:
|
||||
return is12G ? (3000000, 5000000) : (1500000, 3000000);
|
||||
case MetricType.Triangles:
|
||||
return is12G ? (3000000, 5000000) : (1500000, 3000000);
|
||||
case MetricType.TextureMem:
|
||||
return is12G ? (1536L * 1024 * 1024, 2560L * 1024 * 1024) : (1024L * 1024 * 1024, 1536L * 1024 * 1024); // 1.5G/2.5G vs 1G/1.5G
|
||||
case MetricType.MeshMem:
|
||||
return is12G ? (512L * 1024 * 1024, 800L * 1024 * 1024) : (256L * 1024 * 1024, 512L * 1024 * 1024);
|
||||
case MetricType.AudioMem:
|
||||
return is12G ? (100L * 1024 * 1024, 200L * 1024 * 1024) : (50L * 1024 * 1024, 100L * 1024 * 1024);
|
||||
case MetricType.RealtimeLights:
|
||||
return is12G ? (3, 5) : (1, 3);
|
||||
case MetricType.ShadowLights:
|
||||
return is12G ? (1, 2) : (1, 1);
|
||||
case MetricType.TotalLights:
|
||||
return is12G ? (20, 50) : (10, 20); // 包含烘焙灯光
|
||||
case MetricType.SkinnedMesh:
|
||||
return is12G ? (50, 100) : (30, 50);
|
||||
case MetricType.ParticleSystems:
|
||||
return is12G ? (50, 100) : (30, 50);
|
||||
case MetricType.TransparentObjects:
|
||||
return is12G ? (500, 1000) : (300, 500); // 预估数量,非严格标准
|
||||
default:
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourceInfo
|
||||
{
|
||||
public string name;
|
||||
public long size;
|
||||
public Object obj;
|
||||
}
|
||||
|
||||
private class AnalysisData
|
||||
{
|
||||
public int objectCount;
|
||||
public long totalVertices;
|
||||
public long totalTriangles;
|
||||
public long textureMemory;
|
||||
public long meshMemory;
|
||||
public long audioMemory;
|
||||
|
||||
public int uniqueTextureCount;
|
||||
public int uniqueMaterialCount;
|
||||
public int uniqueMeshCount;
|
||||
|
||||
public int realtimeLightCount;
|
||||
public int shadowCastingLights;
|
||||
public int totalLightCount;
|
||||
public bool hasFog;
|
||||
public int skinnedMeshCount;
|
||||
public int particleSystemCount;
|
||||
|
||||
public int transparentObjectCount;
|
||||
public int nonStaticCount;
|
||||
|
||||
// 新增:Missing Script 和 MeshCollider 统计
|
||||
public int missingScriptCount;
|
||||
public int complexMeshColliderCount;
|
||||
|
||||
public List<GameObject> realtimeLights = new List<GameObject>();
|
||||
public List<GameObject> shadowLights = new List<GameObject>();
|
||||
public List<GameObject> skinnedMeshes = new List<GameObject>();
|
||||
public List<GameObject> particleSystems = new List<GameObject>();
|
||||
public List<GameObject> transparentObjects = new List<GameObject>();
|
||||
public List<GameObject> nonStaticObjects = new List<GameObject>();
|
||||
|
||||
public List<GameObject> missingScriptObjects = new List<GameObject>();
|
||||
public List<GameObject> complexMeshColliders = new List<GameObject>();
|
||||
|
||||
// Top Resources
|
||||
public List<ResourceInfo> topTextures = new List<ResourceInfo>();
|
||||
public List<ResourceInfo> topMeshes = new List<ResourceInfo>();
|
||||
}
|
||||
|
||||
// 独立的窗口类用于显示详细列表
|
||||
public class TopResourcesWindow : EditorWindow
|
||||
{
|
||||
private List<ResourceInfo> resources;
|
||||
private Vector2 scroll;
|
||||
|
||||
public static void Show(string title, List<ResourceInfo> resources)
|
||||
{
|
||||
var win = GetWindow<TopResourcesWindow>(true, title, true);
|
||||
win.resources = resources;
|
||||
win.minSize = new Vector2(400, 300);
|
||||
win.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (resources == null) return;
|
||||
|
||||
scroll = EditorGUILayout.BeginScrollView(scroll);
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
EditorGUILayout.LabelField($"资源名称", "大小", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
foreach (var res in resources)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
EditorGUILayout.LabelField(res.name, GUILayout.Width(250));
|
||||
EditorGUILayout.LabelField(EditorUtility.FormatBytes(res.size), GUILayout.Width(80));
|
||||
if (GUILayout.Button("Select", GUILayout.Width(60)))
|
||||
{
|
||||
Selection.activeObject = res.obj;
|
||||
EditorGUIUtility.PingObject(res.obj);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
}
|
||||
|
||||
private enum MetricType
|
||||
{
|
||||
ObjectCount,
|
||||
Vertices,
|
||||
Triangles,
|
||||
TextureMem,
|
||||
MeshMem,
|
||||
AudioMem,
|
||||
RealtimeLights,
|
||||
ShadowLights,
|
||||
TotalLights,
|
||||
SkinnedMesh,
|
||||
ParticleSystems,
|
||||
TransparentObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/SoraTools/SceneAnalyzer/Editor/SceneAnalyzer.cs.meta
Normal file
11
Assets/SoraTools/SceneAnalyzer/Editor/SceneAnalyzer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20681ed358cab224ab1111f4357b83d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user