添加缺失插件

This commit is contained in:
YXY
2026-03-03 18:24:17 +08:00
parent d43882c5cf
commit c979cd2a92
90 changed files with 5269 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7723cab0703e3a74b9e989289246b5c4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f45866dcd12dad3429179152a3985796
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20681ed358cab224ab1111f4357b83d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: