using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; using System.IO; public class AvatarSizeAnalyzer : EditorWindow { private Vector2 scroll; private List entries = new List(); private long totalSize; private class Entry { public string path; public string type; public long size; } [MenuItem("Tools/VRChat/Analyze Selected Avatar")] public static void ShowWindow() { GetWindow("Avatar Size Analyzer"); } private void OnGUI() { EditorGUILayout.LabelField( "VRChat Avatar Dependency Analyzer", EditorStyles.boldLabel ); EditorGUILayout.Space(); GameObject selected = Selection.activeGameObject; if (selected == null) { EditorGUILayout.HelpBox( "Select the ROOT GameObject of your avatar in the Hierarchy.", MessageType.Info ); return; } EditorGUILayout.LabelField("Selected:", selected.name); if (GUILayout.Button("Analyze Avatar")) { Analyze(selected); } EditorGUILayout.Space(); if (entries.Count == 0) return; EditorGUILayout.LabelField( $"Estimated dependency total: {FormatBytes(totalSize)}", EditorStyles.boldLabel ); EditorGUILayout.LabelField( $"Dependencies found: {entries.Count}" ); EditorGUILayout.Space(); scroll = EditorGUILayout.BeginScrollView(scroll); foreach (Entry e in entries) { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button( $"{FormatBytes(e.size),10} {e.type,-18} {e.path}", EditorStyles.label)) { Object obj = AssetDatabase.LoadAssetAtPath(e.path); if (obj != null) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); } } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } private void Analyze(GameObject avatar) { entries.Clear(); totalSize = 0; Object[] dependencies = EditorUtility.CollectDependencies(new Object[] { avatar }); HashSet processedPaths = new HashSet(); foreach (Object dependency in dependencies) { if (dependency == null) continue; string path = AssetDatabase.GetAssetPath(dependency); if (string.IsNullOrEmpty(path)) continue; // Ignore Unity/VRChat package internals. if (path.StartsWith("Packages/")) continue; // Prevent counting the same asset multiple times. if (!processedPaths.Add(path)) continue; long size = GetAssetSize(path); entries.Add(new Entry { path = path, type = dependency.GetType().Name, size = size }); totalSize += size; } entries = entries .OrderByDescending(x => x.size) .ToList(); Debug.Log( $"[Avatar Size Analyzer] {avatar.name}: " + $"{entries.Count} dependencies, " + $"{FormatBytes(totalSize)} source files." ); foreach (Entry e in entries.Take(30)) { Debug.Log( $"{FormatBytes(e.size),10} | {e.type,-20} | {e.path}" ); } } private long GetAssetSize(string assetPath) { string fullPath = Path.GetFullPath(assetPath); if (File.Exists(fullPath)) { try { return new FileInfo(fullPath).Length; } catch { return 0; } } return 0; } private static string FormatBytes(long bytes) { if (bytes >= 1024L * 1024L * 1024L) return $"{bytes / (1024f * 1024f * 1024f):0.00} GB"; if (bytes >= 1024L * 1024L) return $"{bytes / (1024f * 1024f):0.00} MB"; if (bytes >= 1024L) return $"{bytes / 1024f:0.00} KB"; return $"{bytes} B"; } }