| New file |
| | |
| | | using UnityEngine; |
| | | using UnityEditor; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | |
| | | /// <summary> |
| | | /// 战斗预加载资源监控窗口 |
| | | /// </summary> |
| | | 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<BattleCacheManager.ResourceReferenceView> spineResourceViews = new List<BattleCacheManager.ResourceReferenceView>(); |
| | | private List<BattleCacheManager.ResourceReferenceView> audioResourceViews = new List<BattleCacheManager.ResourceReferenceView>(); |
| | | private HashSet<string> allBattleGuids = new HashSet<string>(); |
| | | |
| | | private GUIStyle headerStyle; |
| | | private GUIStyle evenRowStyle; |
| | | private GUIStyle oddRowStyle; |
| | | private GUIStyle loadedStyle; |
| | | private GUIStyle unloadedStyle; |
| | | |
| | | [MenuItem("Tools/战斗/预加载资源监控")] |
| | | public static void ShowWindow() |
| | | { |
| | | var window = GetWindow<BattlePreloadResWatcher>("战斗资源监控"); |
| | | 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<BattleCacheManager.ResourceReferenceView> 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(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 在编辑器中选中资源 |
| | | /// </summary> |
| | | 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<Object>(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<Object>(bestMatch); |
| | | if (asset != null) |
| | | { |
| | | Selection.activeObject = asset; |
| | | EditorGUIUtility.PingObject(asset); |
| | | Debug.Log($"通过搜索找到资源: {bestMatch}\n(原始路径: {resourcePath})"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | Debug.LogWarning($"未找到资源: {resourcePath}\n尝试的基础路径: Assets/ResourcesOut/{resourcePath}[extensions]"); |
| | | } |
| | | } |
| | | } |