三国卡牌客户端基础资源仓库
yyl
昨天 b689754acfb6b2503631bbfb019cbbed04fd7b5f
125 战斗 新增预加载资源编辑器查看状态功能
2个文件已添加
407 ■■■■■ 已修改文件
Assets/Editor/Tool/BattlePreloadResWatcher.cs 396 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/Tool/BattlePreloadResWatcher.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/Tool/BattlePreloadResWatcher.cs
New file
@@ -0,0 +1,396 @@
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]");
        }
    }
}
Assets/Editor/Tool/BattlePreloadResWatcher.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c91f4ead846c5154db3aa7ed7b93eb8a
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant: