本人能力有限,如有不足还请斧正
目录
初版演示:unity 事件中心日志小工具讲解,再也不怕乱用事件中心了_哔哩哔哩_bilibili
跳转功能演示视频:Unity 事件中心管理小工具支持点击跳转脚本嘻嘻_哔哩哔哩_bilibili
一.设计思路:
1.通过过滤指定程序集和并使用正则表达式匹配事件中心的订阅和触发的关键字
2.将匹配到的事件信息存入对应的字典中 并做缓存功能
3.通过odin插件的button添加按钮 执行方法
4.将脚本写为编辑器拓展类 并可以创建so文件在编辑器模式下全局使用 或者 运行时作为日志使用
数据结构设计如下:
EventRecord 类
存储单个事件记录的信息,包括脚本路径、行号和事件名称
在编辑器模式下提供按钮,点击可直接定位到脚本中的事件位置
EventRegister 类
继承自 ScriptableObject,作为配置文件存储扫描结果
使用两个字典分别存储事件注册信息和触发信息
记录扫描的脚本总数和扫描时间
工具源码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;
using Sirenix.OdinInspector;
#if UNITY_EDITOR
using UnityEditor;
#endif
[Serializable,HideReferenceObiectPicker]
public class EventRecord
{
[HideInInspector]
public string ScriptPath;
[HideInInspector]
public int Line;
[ShowInInspector, HideLabel]
public string Name;
#if UNITY_EDITOR
[Button("$Name", ButtonSizes.Small), GUIColor(0.8f, 1f, 0.6f)]
private void Open()
{
var mono = AssetDatabase.LoadAssetAtPath<MonoScript>(ScriptPath);
if (mono != null)
AssetDatabase.OpenAsset(mono, Line);
else
Debug.LogError($"无法加载脚本: {ScriptPath}");
}
#endif
}
[CreateAssetMenu(fileName = "EventRegisterSettings", menuName = "MieMieFrameTools/EventCenter/EventRegister")]
public class EventRegister : SerializedScriptableObject
{
[FoldoutGroup("事件注册信息", expanded: true)]
[DictionaryDrawerSettings(KeyLabel = "监听脚本", ValueLabel = "事件列表", DisplayMode = DictionaryDisplayOptions.ExpandedFoldout)]
public Dictionary<Type, List<EventRecord>> eventAddLisenerInfo = new Dictionary<Type, List<EventRecord>>();
[FoldoutGroup("事件触发信息", expanded: true)]
[DictionaryDrawerSettings(KeyLabel = "触发脚本", ValueLabel = "事件列表", DisplayMode = DictionaryDisplayOptions.ExpandedFoldout)]
public Dictionary<Type, List<EventRecord>> eventTriggerInfo = new Dictionary<Type, List<EventRecord>>();
[SerializeField, ReadOnly]
private int totalScriptsScanned = 0;
[SerializeField, ReadOnly]
private float scanTime = 0f;
#if UNITY_EDITOR
[Button("刷新事件记录", ButtonSizes.Large), GUIColor(0.4f, 0.8f, 1f)]
public void RefreshEventRecord()
{
try
{
var startTime = DateTime.Now;
EditorUtility.DisplayProgressBar("事件中心", "开始扫描脚本...", 0f);
ClearEventRecord();
ScanTargetScripts();
scanTime = (float)(DateTime.Now - startTime).TotalSeconds;
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
Debug.Log($"扫描完成! 处理 {totalScriptsScanned} 个脚本,耗时 {scanTime:F2}s");
}
finally
{
EditorUtility.ClearProgressBar();
}
}
private void ScanTargetScripts()
{
string[] guids = AssetDatabase.FindAssets("t:Script");
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
EditorUtility.DisplayProgressBar("扫描进度", $"处理: {Path.GetFileName(path)}", (float)i / guids.Length);
ProcessScript(path);
}
}
private void ProcessScript(string path)
{
try
{
MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (monoScript == null) return;
Type type = monoScript.GetClass();
if (type == null)
{
Debug.LogWarning($"无法解析类型: {Path.GetFileName(path)}");
return;
}
if (!IsTargetAssembly(type))
{
Debug.Log($"跳过程序集: {type.Assembly.GetName().Name} -> {type.FullName}");
return;
}
string content = File.ReadAllText(path);
FindEventCalls(content, type, path);
totalScriptsScanned++;
}
catch (Exception e)
{
Debug.LogWarning($"解析失败 {path}: {e.Message}");
}
}
private bool IsTargetAssembly(Type type)
{
string[] validAssemblies = { "Assembly-CSharp", "Assembly-CSharp-firstpass", "MyGameScripts" };
return validAssemblies.Contains(type.Assembly.GetName().Name);
}
private void FindEventCalls(string content, Type type, string path)
{
const string eventPattern =
@"EventCenter\.(AddEventListener|RemoveListener|TriggerEvent)\s*" +
@"(?:<[^>]+>)?\s*\(\s*[^,]*?""([^""]+)""";
var matches = Regex.Matches(content, eventPattern, RegexOptions.Singleline | RegexOptions.Compiled);
foreach (Match match in matches)
{
string callType = match.Groups[1].Value;
string evtName = match.Groups[2].Value.Trim();
int line = GetLineNumber(content, match.Index);
var targetDict = callType == "AddEventListener"
? eventAddLisenerInfo
: callType == "TriggerEvent"
? eventTriggerInfo
: null;
if (targetDict != null)
{
AddToDictionary(targetDict, type, new EventRecord { Name = evtName, ScriptPath = path, Line = line });
}
}
}
private int GetLineNumber(string content, int index)
{
return content.Take(index).Count(c => c == '\n') + 1;
}
private void AddToDictionary(Dictionary<Type, List<EventRecord>> dict, Type type, EventRecord record)
{
if (string.IsNullOrEmpty(record.Name)) return;
if (!dict.TryGetValue(type, out var list))
{
list = new List<EventRecord>();
dict[type] = list;
}
if (!list.Any(r => r.Name == record.Name && r.ScriptPath == record.ScriptPath && r.Line == record.Line))
{
list.Add(record);
}
}
#endif
[Button("清空记录", ButtonSizes.Large), GUIColor(1f, 0.6f, 0.6f)]
public void ClearEventRecord()
{
eventAddLisenerInfo.Clear();
eventTriggerInfo.Clear();
totalScriptsScanned = 0;
scanTime = 0f;
}
}
二.修改思路
匹配关键字修改
因为该工具是匹配关键字 并不影响原来的事件中心脚本
需要配套的事件中心去使用 如果你想改为自己的工具也就是匹配自己的事件中心
直接修改下图的关键字即可
匹配程序集修改
如果你觉得扫描速度太慢(实测70个脚本扫描了两秒) 可以自行定义程序集
只需要获取想扫描的名字即可,下为如何获取程序集名
配套事件中心源码
请自行去掉命名空间
namespace MieMieFrameTools
{
using System;
using System.Collections.Generic;
public static class EventCenter
{
#region 内部类
private interface IEventInfo
{
void RemoveAllAction();
}
private class EventInfo : IEventInfo
{
private Action action;
public void AddAction(Action action)
{
this.action += action;
}
public void RemoveAction(Action action)
{
this.action -= action;
}
public void RemoveAllAction()
{
this.action = null;
this.PoolPushObject(); //放回对象池
}
public void Trigger()
{
action?.Invoke();
}
}
private class EventInfo<T> : IEventInfo
{
public Action<T> action;
public void AddAction(Action<T> action)
{
this.action = action;
}
public void RemoveAction(Action<T> action)
{
this.action -= action;
}
public void Trigger(T arg)
{
action?.Invoke(arg);
}
public void RemoveAllAction()
{
this.action = null;
this.PoolPushObject();
}
}
private class EventInfo<T0, T1> : IEventInfo
{
private Action<T0, T1> action;
public void AddAction(Action<T0, T1> action)
{
this.action = action;
}
public void RemoveAction(Action<T0, T1> action)
{
this.action -= action;
}
public void Trigger(T0 arg0, T1 arg1)
{
action?.Invoke(arg0, arg1);
}
public void RemoveAllAction()
{
this.action = null;
this.PoolPushObject();
}
}
private class EventInfo<T0, T1, T2> : IEventInfo
{
private Action<T0, T1, T2> action;
public void AddAction(Action<T0, T1, T2> action)
{
this.action = action;
}
public void RemoveAction(Action<T0, T1, T2> action)
{
this.action -= action;
}
public void Trigger(T0 arg0, T1 arg1, T2 arg2)
{
action?.Invoke(arg0, arg1, arg2);
}
public void RemoveAllAction()
{
this.action = null;
this.PoolPushObject();
}
}
private class EventInfo<T0, T1, T2, T3> : IEventInfo
{
private Action<T0, T1, T2, T3> action;
public void AddAction(Action<T0, T1, T2, T3> action)
{
this.action = action;
}
public void RemoveAction(Action<T0, T1, T2, T3> action)
{
this.action -= action;
}
public void Trigger(T0 arg0, T1 arg1, T2 arg2, T3 arg3)
{
action?.Invoke(arg0, arg1, arg2, arg3);
}
public void RemoveAllAction()
{
this.action = null;
this.PoolPushObject();
}
}
private class EventInfo<T0, T1, T2, T3, T4> : IEventInfo
{
private Action<T0, T1, T2, T3, T4> action;
public void AddAction(Action<T0, T1, T2, T3, T4> action)
{
this.action = action;
}
public void RemoveAction(Action<T0, T1, T2, T3, T4> action)
{
this.action -= action;
}
public void Trigger(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
action?.Invoke(arg0, arg1, arg2, arg3, arg4);
}
public void RemoveAllAction()
{
this.action = null;
this.PoolPushObject();
}
}
#endregion
// 事件字典
private static Dictionary<string, IEventInfo> eventInfoDict = new Dictionary<string, IEventInfo>();
#region 添加事件监听
public static void AddEventListener(string name, Action action)
{
if (eventInfoDict.ContainsKey(name))
{
(eventInfoDict[name] as EventInfo).AddAction(action);
}
else
{
//通过PoolManager获取 或 new一个EventInfo对象
EventInfo eventInfo = PoolManager.Instance.GetObject<EventInfo>();
eventInfo.AddAction(action);
eventInfoDict.Add(name, eventInfo);
}
}
public static void AddEventListener<T>(string name, Action<T> action)
{
if (eventInfoDict.ContainsKey(name))
{
(eventInfoDict[name] as EventInfo<T>).action += action;
}
else
{
EventInfo<T> eventInfo = PoolManager.Instance.GetObject<EventInfo<T>>();
eventInfo.AddAction(action);
eventInfoDict.Add(name, eventInfo);
}
}
public static void AddEventListener<T0, T1>(string name, Action<T0, T1> action)
{
if (eventInfoDict.ContainsKey(name))
{
(eventInfoDict[name] as EventInfo<T0, T1>).AddAction(action);
}
else
{
EventInfo<T0, T1> eventInfo = PoolManager.Instance.GetObject<EventInfo<T0, T1>>();
eventInfo.AddAction(action);
eventInfoDict.Add(name, eventInfo);
}
}
public static void AddEventListener<T0, T1, T2>(string name, Action<T0, T1, T2> action)
{
if (eventInfoDict.ContainsKey(name))
{
(eventInfoDict[name] as EventInfo<T0, T1, T2>).AddAction(action);
}
else
{
EventInfo<T0, T1, T2> eventInfo = PoolManager.Instance.GetObject<EventInfo<T0, T1, T2>>();
eventInfo.AddAction(action);
eventInfoDict.Add(name, eventInfo);
}
}
public static void AddEventListener<T0, T1, T2, T3>(string name, Action<T0, T1, T2, T3> action)
{
if (eventInfoDict.ContainsKey(name))
{
(eventInfoDict[name] as EventInfo<T0, T1, T2, T3>).AddAction(action);
}
else
{
EventInfo<T0, T1, T2, T3> eventInfo = PoolManager.Instance.GetObject<EventInfo<T0, T1, T2, T3>>();
eventInfo.AddAction(action);
eventInfoDict.Add(name, eventInfo);
}
}
public static void AddEventListener<T0, T1, T2, T3, T4>(string name, Action<T0, T1, T2, T3, T4> action)
{
if (eventInfoDict.ContainsKey(name))
{
(eventInfoDict[name] as EventInfo<T0, T1, T2, T3, T4>).AddAction(action);
}
else
{
EventInfo<T0, T1, T2, T3, T4> eventInfo = PoolManager.Instance.GetObject<EventInfo<T0, T1, T2, T3, T4>>();
eventInfo.AddAction(action);
eventInfoDict.Add(name, eventInfo);
}
}
#endregion
#region 触发事件
public static void TriggerEvent(string name)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo)).Trigger();
}
}
public static void TriggerEvent<T>(string name, T args)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T>)).Trigger(args);
}
}
public static void TriggerEvent<T0, T1>(string name, T0 arg0, T1 arg1)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1>)).Trigger(arg0, arg1);
}
}
public static void TriggerEvent<T0, T1, T2>(string name, T0 arg0, T1 arg1, T2 arg2)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1, T2>)).Trigger(arg0, arg1, arg2);
}
}
public static void TriggerEvent<T0, T1, T2, T3>(string name, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1, T2, T3>)).Trigger(arg0, arg1, arg2, arg3);
}
}
public static void TriggerEvent<T0, T1, T2, T3, T4>(string name, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1, T2, T3, T4>)).Trigger(arg0, arg1, arg2, arg3, arg4);
}
}
#endregion
#region 删除某一个事件的指定回调
/// <summary>
/// 删除某一个事件的指定回调
/// </summary>
/// <param name="name"></param>
/// <param name="action"></param>
public static void RemoveListener(string name, Action action)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo)).RemoveAction(action);
}
}
/// <summary>
/// 删除某一个事件的指定回调
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <param name="action"></param>
public static void RemoveListener<T>(string name, Action<T> action)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T>)).RemoveAction(action);
}
}
/// <summary>
/// 删除某一个事件的指定回调
/// </summary>
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <param name="name"></param>
/// <param name="action"></param>
public static void RemoveListener<T0, T1>(string name, Action<T0, T1> action)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1>)).RemoveAction(action);
}
}
/// <summary>
/// 删除某一个事件的指定回调
/// </summary>
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="name"></param>
/// <param name="action"></param>
public static void RemoveListener<T0, T1, T2>(string name, Action<T0, T1, T2> action)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1, T2>)).RemoveAction(action);
}
}
/// <summary>
/// 删除某一个事件的指定回调
/// </summary>
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <param name="name"></param>
/// <param name="action"></param>
public static void RemoveListener<T0, T1, T2, T3>(string name, Action<T0, T1, T2, T3> action)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1, T2, T3>)).RemoveAction(action);
}
}
/// <summary>
/// 删除某一个事件的指定回调
/// </summary>
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <param name="name"></param>
/// <param name="action"></param>
public static void RemoveListener<T0, T1, T2, T3, T4>(string name, Action<T0, T1, T2, T3, T4> action)
{
if (eventInfoDict.ContainsKey(name))
{
((eventInfoDict[name] as EventInfo<T0, T1, T2, T3, T4>)).RemoveAction(action);
}
}
#endregion
#region 移除所有事件监听 && 清空事件字典
/// <summary>
/// 移除指定事件的所有回调 , 某一个事件不需要了时调用
/// </summary>
/// <param name="name"></param>
public static void RemoveListener(string name)
{
if (eventInfoDict.ContainsKey(name))
{
eventInfoDict[name].RemoveAllAction();
eventInfoDict.Remove(name);
}
}
/// <summary>
/// 移除所有事件的所有回调 退出游戏时调用
/// </summary>
public static void ClearAllListeners()
{
foreach (string name in eventInfoDict.Keys)
{
eventInfoDict[name].RemoveAllAction();
}
eventInfoDict.Clear();
}
#endregion
}
}