using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; namespace BaseGames.Editor.Modules { /// /// DataHub 事件频道反查模块 —— 列出项目中所有 EventChannelSO 子类资产; /// 选中后可扫描全项目中引用该频道的 Prefab / ScriptableObject, /// 解决"谁发布/谁订阅了某条事件"在大型项目中难以追踪的问题。 /// /// 反查实现:遍历所有 Prefab 与 SO 资产,用 AssetDatabase.GetDependencies /// 检查依赖链是否包含目标频道资产路径。首次扫描会耗时较长(取决于资产规模), /// 之后按选中频道增量计算。 /// public class EventChannelModule : IDataModule, IDataModuleOrdered { public string ModuleId => "eventchannel"; public string DisplayName => "事件频道"; public string IconName => "d_AudioMixerController Icon"; public int DisplayOrder => 200; private ListView _channelList; private readonly List _allChannels = new(); private ScriptableObject _selected; private VisualElement _refsContainer; private struct EventChannelEntry { public string TypeName; public string AssetPath; public ScriptableObject Asset; } public void Initialize() => Refresh(); public void OnActivated() => Refresh(); private void Refresh() { _allChannels.Clear(); // 扫描所有 ScriptableObject 子类,筛选名称以 "EventChannelSO" 结尾的(项目约定)。 var types = TypeCache.GetTypesDerivedFrom(); foreach (var t in types) { if (t.IsAbstract) continue; if (!t.Name.EndsWith("EventChannelSO", StringComparison.Ordinal)) continue; var guids = AssetDatabase.FindAssets($"t:{t.Name}"); foreach (var g in guids) { var path = AssetDatabase.GUIDToAssetPath(g); var asset = AssetDatabase.LoadAssetAtPath(path); if (asset == null) continue; _allChannels.Add(new EventChannelEntry { TypeName = t.Name, AssetPath = path, Asset = asset }); } } _allChannels.Sort((a, b) => { int c = string.Compare(a.TypeName, b.TypeName, StringComparison.Ordinal); return c != 0 ? c : string.Compare(a.AssetPath, b.AssetPath, StringComparison.Ordinal); }); _channelList?.Rebuild(); } public void BuildListPane(VisualElement container, Action onSelected) { var header = new Label($"频道总数:{_allChannels.Count}"); header.style.unityFontStyleAndWeight = FontStyle.Bold; header.style.paddingLeft = 6; header.style.paddingTop = 4; container.Add(header); _channelList = new ListView(_allChannels, 20, () => new Label { style = { paddingLeft = 6 } }, (ve, i) => { var entry = _allChannels[i]; var label = (Label)ve; label.text = $"[{entry.TypeName}] {System.IO.Path.GetFileNameWithoutExtension(entry.AssetPath)}"; label.tooltip = entry.AssetPath; }); _channelList.style.flexGrow = 1; _channelList.selectionChanged += sel => { var first = sel.FirstOrDefault(); if (first is EventChannelEntry e) { _selected = e.Asset; onSelected?.Invoke(e.Asset); } }; container.Add(_channelList); } public void BuildDetailPane(VisualElement container, UnityEngine.Object selected) { _selected = selected as ScriptableObject; container.Clear(); if (_selected == null) { var hint = new Label("请在左侧选择一条频道资产。"); hint.style.paddingLeft = 8; hint.style.paddingTop = 8; container.Add(hint); return; } // Header var title = new Label($"{_selected.GetType().Name} · {_selected.name}"); title.style.unityFontStyleAndWeight = FontStyle.Bold; title.style.fontSize = 14; title.style.paddingLeft = 8; title.style.paddingTop = 6; container.Add(title); var pathLabel = new Label(AssetDatabase.GetAssetPath(_selected)) { style = { paddingLeft = 8, paddingBottom = 6, color = new StyleColor(new Color(0.7f, 0.7f, 0.7f)) } }; container.Add(pathLabel); var scanBtn = new Button(() => ScanReferences(_selected)) { text = "扫描全项目引用(Prefab / ScriptableObject)" }; scanBtn.style.marginLeft = 8; scanBtn.style.marginRight = 8; container.Add(scanBtn); _refsContainer = new VisualElement(); _refsContainer.style.paddingLeft = 8; _refsContainer.style.paddingTop = 6; container.Add(_refsContainer); } private void ScanReferences(ScriptableObject channel) { if (channel == null || _refsContainer == null) return; _refsContainer.Clear(); var targetPath = AssetDatabase.GetAssetPath(channel); if (string.IsNullOrEmpty(targetPath)) return; var found = new List(); try { EditorUtility.DisplayProgressBar("扫描引用", "正在扫描 Prefab 与 SO …", 0f); var prefabGuids = AssetDatabase.FindAssets("t:Prefab"); var soGuids = AssetDatabase.FindAssets("t:ScriptableObject"); var all = prefabGuids.Concat(soGuids).ToArray(); for (int i = 0; i < all.Length; i++) { var p = AssetDatabase.GUIDToAssetPath(all[i]); if (p == targetPath) continue; if (i % 64 == 0) EditorUtility.DisplayProgressBar("扫描引用", p, (float)i / all.Length); var deps = AssetDatabase.GetDependencies(p, recursive: false); if (System.Array.IndexOf(deps, targetPath) >= 0) found.Add(p); } } finally { EditorUtility.ClearProgressBar(); } var summary = new Label($"找到 {found.Count} 个直接引用:"); summary.style.unityFontStyleAndWeight = FontStyle.Bold; _refsContainer.Add(summary); foreach (var path in found) { var btn = new Button(() => { var obj = AssetDatabase.LoadMainAssetAtPath(path); EditorGUIUtility.PingObject(obj); Selection.activeObject = obj; }) { text = path }; btn.style.unityTextAlign = TextAnchor.MiddleLeft; btn.style.marginTop = 2; _refsContainer.Add(btn); } } } }