Files
zeling_v2/Assets/_Game/Scripts/Editor/Modules/EventChannelModule.cs
2026-05-25 13:21:41 +08:00

190 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}
}