190 lines
7.5 KiB
C#
190 lines
7.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
|
||
namespace BaseGames.Editor.Modules
|
||
{
|
||
/// <summary>
|
||
/// DataHub 事件频道反查模块 —— 列出项目中所有 EventChannelSO 子类资产;
|
||
/// 选中后可扫描全项目中引用该频道的 Prefab / ScriptableObject,
|
||
/// 解决"谁发布/谁订阅了某条事件"在大型项目中难以追踪的问题。
|
||
///
|
||
/// 反查实现:遍历所有 Prefab 与 SO 资产,用 AssetDatabase.GetDependencies
|
||
/// 检查依赖链是否包含目标频道资产路径。首次扫描会耗时较长(取决于资产规模),
|
||
/// 之后按选中频道增量计算。
|
||
/// </summary>
|
||
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<EventChannelEntry> _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<ScriptableObject>();
|
||
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<ScriptableObject>(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<UnityEngine.Object> 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<string>();
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|