using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; /// /// 战斗预加载资源监控窗口 /// public class BattlePreloadResWatcher : EditorWindow { private Vector2 scrollPosition; private bool showSpineResources = true; private bool showAudioResources = true; private string filterText = ""; private bool autoRefresh = true; private double lastRefreshTime = 0; private const double AUTO_REFRESH_INTERVAL = 1.0; // 1秒自动刷新 private List spineResourceViews = new List(); private List audioResourceViews = new List(); private HashSet allBattleGuids = new HashSet(); private GUIStyle headerStyle; private GUIStyle evenRowStyle; private GUIStyle oddRowStyle; private GUIStyle loadedStyle; private GUIStyle unloadedStyle; [MenuItem("Tools/战斗/预加载资源监控")] public static void ShowWindow() { var window = GetWindow("战斗资源监控"); window.minSize = new Vector2(800, 400); window.Show(); } private void OnEnable() { RefreshData(); } private void OnGUI() { InitStyles(); // 顶部工具栏 DrawToolbar(); // 统计信息 DrawStatistics(); GUILayout.Space(5); // 滚动区域 scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); // Spine资源 if (showSpineResources) { DrawResourceSection("Spine 资源", spineResourceViews); } GUILayout.Space(10); // Audio资源 if (showAudioResources) { DrawResourceSection("Audio 资源", audioResourceViews); } EditorGUILayout.EndScrollView(); // 自动刷新 if (autoRefresh && EditorApplication.timeSinceStartup - lastRefreshTime > AUTO_REFRESH_INTERVAL) { RefreshData(); lastRefreshTime = EditorApplication.timeSinceStartup; Repaint(); } } private void InitStyles() { if (headerStyle == null) { headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14, normal = { textColor = Color.white } }; evenRowStyle = new GUIStyle(EditorStyles.label) { normal = { background = MakeTexture(2, 2, new Color(0.3f, 0.3f, 0.3f, 0.1f)) } }; oddRowStyle = new GUIStyle(EditorStyles.label) { normal = { background = MakeTexture(2, 2, new Color(0.3f, 0.3f, 0.3f, 0.3f)) } }; loadedStyle = new GUIStyle(EditorStyles.label) { normal = { textColor = Color.green } }; unloadedStyle = new GUIStyle(EditorStyles.label) { normal = { textColor = Color.red } }; } } private Texture2D MakeTexture(int width, int height, Color color) { Color[] pixels = new Color[width * height]; for (int i = 0; i < pixels.Length; i++) { pixels[i] = color; } Texture2D texture = new Texture2D(width, height); texture.SetPixels(pixels); texture.Apply(); return texture; } private void DrawToolbar() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); if (GUILayout.Button("刷新数据", EditorStyles.toolbarButton, GUILayout.Width(80))) { RefreshData(); } autoRefresh = GUILayout.Toggle(autoRefresh, "自动刷新", EditorStyles.toolbarButton, GUILayout.Width(80)); GUILayout.Space(10); showSpineResources = GUILayout.Toggle(showSpineResources, "显示Spine", EditorStyles.toolbarButton, GUILayout.Width(80)); showAudioResources = GUILayout.Toggle(showAudioResources, "显示Audio", EditorStyles.toolbarButton, GUILayout.Width(80)); GUILayout.Space(10); GUILayout.Label("筛选:", GUILayout.Width(40)); filterText = GUILayout.TextField(filterText, EditorStyles.toolbarTextField, GUILayout.Width(200)); GUILayout.FlexibleSpace(); if (GUILayout.Button("清空缓存", EditorStyles.toolbarButton, GUILayout.Width(80))) { if (EditorUtility.DisplayDialog("确认", "确定要清空所有战斗资源缓存吗?", "确定", "取消")) { BattleCacheManager.DebugAPI.ClearAllCache(); RefreshData(); } } EditorGUILayout.EndHorizontal(); } private void DrawStatistics() { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("缓存统计", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"Spine 资源总数: {spineResourceViews.Count}", GUILayout.Width(200)); EditorGUILayout.LabelField($"Audio 资源总数: {audioResourceViews.Count}", GUILayout.Width(200)); EditorGUILayout.LabelField($"战场数量: {allBattleGuids.Count}", GUILayout.Width(200)); EditorGUILayout.EndHorizontal(); int spineLoaded = spineResourceViews.Count(v => v.IsLoaded); int audioLoaded = audioResourceViews.Count(v => v.IsLoaded); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"Spine 已加载: {spineLoaded}", GUILayout.Width(200)); EditorGUILayout.LabelField($"Audio 已加载: {audioLoaded}", GUILayout.Width(200)); EditorGUILayout.LabelField($"总引用计数: {spineResourceViews.Sum(v => v.TotalRefCount) + audioResourceViews.Sum(v => v.TotalRefCount)}", GUILayout.Width(200)); EditorGUILayout.EndHorizontal(); // 战场列表 if (allBattleGuids.Count > 0) { GUILayout.Space(5); EditorGUILayout.LabelField("活跃战场:", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); foreach (var guid in allBattleGuids) { string displayGuid = string.IsNullOrEmpty(guid) ? "[主战场]" : guid.Substring(0, Mathf.Min(8, guid.Length)) + "..."; EditorGUILayout.LabelField($"• {displayGuid}", GUILayout.Width(150)); } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); } private void DrawResourceSection(string title, List resources) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField(title, headerStyle); GUILayout.Space(5); // 表头 EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); EditorGUILayout.LabelField("资源路径", EditorStyles.toolbarButton, GUILayout.Width(600)); EditorGUILayout.LabelField("状态", EditorStyles.toolbarButton, GUILayout.Width(60)); EditorGUILayout.LabelField("引用计数", EditorStyles.toolbarButton, GUILayout.Width(80)); EditorGUILayout.LabelField("战场分布", EditorStyles.toolbarButton, GUILayout.ExpandWidth(true)); EditorGUILayout.EndHorizontal(); // 筛选资源 var filteredResources = resources; if (!string.IsNullOrEmpty(filterText)) { filteredResources = resources.Where(r => r.ResourcePath.ToLower().Contains(filterText.ToLower())).ToList(); } // 创建居中样式 GUIStyle centeredStyle = new GUIStyle(GUI.skin.label); centeredStyle.alignment = TextAnchor.MiddleCenter; GUIStyle centeredLoadedStyle = new GUIStyle(loadedStyle); centeredLoadedStyle.alignment = TextAnchor.MiddleCenter; GUIStyle centeredUnloadedStyle = new GUIStyle(unloadedStyle); centeredUnloadedStyle.alignment = TextAnchor.MiddleCenter; // 资源路径样式(可点击) GUIStyle pathStyle = new GUIStyle(GUI.skin.label); pathStyle.wordWrap = false; pathStyle.normal.textColor = new Color(0.4f, 0.7f, 1f); // 淡蓝色表示可点击 // 资源行 for (int i = 0; i < filteredResources.Count; i++) { var resource = filteredResources[i]; var rowStyle = (i % 2 == 0) ? evenRowStyle : oddRowStyle; EditorGUILayout.BeginHorizontal(rowStyle); // 资源路径(可点击,添加 Tooltip) GUIContent pathContent = new GUIContent(resource.ResourcePath, "点击选中资源\n" + resource.ResourcePath); Rect pathRect = GUILayoutUtility.GetRect(pathContent, pathStyle, GUILayout.Width(600)); if (GUI.Button(pathRect, pathContent, pathStyle)) { // 点击时选中资源 SelectAssetInEditor(resource.ResourcePath); } // 鼠标悬停时显示手型光标 EditorGUIUtility.AddCursorRect(pathRect, MouseCursor.Link); // 加载状态(居中) var statusStyle = resource.IsLoaded ? centeredLoadedStyle : centeredUnloadedStyle; EditorGUILayout.LabelField(resource.IsLoaded ? "已加载" : "未加载", statusStyle, GUILayout.Width(60)); // 引用计数(居中) EditorGUILayout.LabelField(resource.TotalRefCount.ToString(), centeredStyle, GUILayout.Width(80)); // 战场分布 string battleInfo = string.Join(", ", resource.BattlefieldRefCounts.Select(kvp => { string guidDisplay = string.IsNullOrEmpty(kvp.Key) ? "[主]" : kvp.Key.Substring(0, Mathf.Min(4, kvp.Key.Length)); return $"{guidDisplay}({kvp.Value})"; })); EditorGUILayout.LabelField(battleInfo, GUILayout.ExpandWidth(true)); EditorGUILayout.EndHorizontal(); } if (filteredResources.Count == 0) { EditorGUILayout.LabelField("没有资源数据", EditorStyles.centeredGreyMiniLabel); } EditorGUILayout.EndVertical(); } private void RefreshData() { spineResourceViews.Clear(); audioResourceViews.Clear(); allBattleGuids.Clear(); // 获取Spine资源 var spineCache = BattleCacheManager.DebugAPI.GetSpineCache(); foreach (var kvp in spineCache) { spineResourceViews.Add(new BattleCacheManager.ResourceReferenceView(kvp.Key, kvp.Value)); } // 获取Audio资源 var audioCache = BattleCacheManager.DebugAPI.GetAudioCache(); foreach (var kvp in audioCache) { audioResourceViews.Add(new BattleCacheManager.ResourceReferenceView(kvp.Key, kvp.Value)); } // 获取所有战场GUID allBattleGuids = BattleCacheManager.DebugAPI.GetAllBattleGuids(); // 排序 spineResourceViews = spineResourceViews.OrderBy(v => v.ResourcePath).ToList(); audioResourceViews = audioResourceViews.OrderBy(v => v.ResourcePath).ToList(); } /// /// 在编辑器中选中资源 /// private void SelectAssetInEditor(string resourcePath) { // resourcePath 格式: directory/assetName (例如: Battle/Audio/bgm_001) // 需要转换为: Assets/ResourcesOut/{directory}/{assetName}{extension} // 尝试多种可能的扩展名 string[] possibleExtensions = new string[] { ".asset", // SkeletonDataAsset ".prefab", // Prefabs ".png", // Textures ".jpg", // Textures ".wav", // Audio ".mp3", // Audio ".ogg", // Audio ".bytes", // Binary data "", // 无扩展名 }; Object asset = null; string foundPath = null; // 尝试所有可能的路径 foreach (var ext in possibleExtensions) { string fullPath = $"Assets/ResourcesOut/{resourcePath}{ext}"; asset = AssetDatabase.LoadAssetAtPath(fullPath); if (asset != null) { foundPath = fullPath; break; } } if (asset != null) { // 在 Project 窗口中选中并高亮 Selection.activeObject = asset; EditorGUIUtility.PingObject(asset); Debug.Log($"已选中资源: {foundPath}"); } else { // 如果找不到资源,尝试使用 AssetDatabase.FindAssets 进行模糊搜索 string assetName = System.IO.Path.GetFileName(resourcePath); string[] guids = AssetDatabase.FindAssets(assetName); if (guids != null && guids.Length > 0) { // 找到匹配的资源,优先选择路径最匹配的 string bestMatch = null; foreach (var guid in guids) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); if (assetPath.Contains(resourcePath)) { bestMatch = assetPath; break; } } // 如果没有完全匹配,使用第一个结果 if (bestMatch == null) { bestMatch = AssetDatabase.GUIDToAssetPath(guids[0]); } asset = AssetDatabase.LoadAssetAtPath(bestMatch); if (asset != null) { Selection.activeObject = asset; EditorGUIUtility.PingObject(asset); Debug.Log($"通过搜索找到资源: {bestMatch}\n(原始路径: {resourcePath})"); return; } } Debug.LogWarning($"未找到资源: {resourcePath}\n尝试的基础路径: Assets/ResourcesOut/{resourcePath}[extensions]"); } } }