Skip to content

Commit 1c5f602

Browse files
authored
Merge pull request #3 from danigfedev/Event_Mapping_Editor_Tool
Event mapping editor tool implemented
2 parents c81d356 + ba3c646 commit 1c5f602

File tree

4 files changed

+304
-1
lines changed

4 files changed

+304
-1
lines changed

Editor/AuditorTool.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using SOBaseEvents;
4+
using UnityEditor;
5+
using UnityEditor.SceneManagement;
6+
using UnityEngine;
7+
8+
namespace Editor.AuditorTool
9+
{
10+
public class EventSystemAuditor : EditorWindow
11+
{
12+
private const string PrefabExtension = ".prefab";
13+
private const string SceneExtension = ".unity";
14+
15+
private Vector2 _scrollPos;
16+
private List<ScriptableObject> _allProjectEvents = new List<ScriptableObject>();
17+
private Dictionary<ScriptableObject, bool> _expandedStates = new Dictionary<ScriptableObject, bool>();
18+
private Dictionary<ScriptableObject, Dictionary<string, List<UsageDetail>>> _masterResults =
19+
new Dictionary<ScriptableObject, Dictionary<string, List<UsageDetail>>>();
20+
private GUIStyle _headerStyle;
21+
22+
private struct UsageDetail
23+
{
24+
public string GameObjectName;
25+
public string ComponentTypeName;
26+
public Object Context;
27+
}
28+
29+
[MenuItem("EspidiGames/SO Events/SO Event System Auditor")]
30+
public static void ShowWindow()
31+
{
32+
GetWindow<EventSystemAuditor>("Event Auditor");
33+
}
34+
35+
private void OnEnable()
36+
{
37+
GetWindow<EventSystemAuditor>("Event Auditor");
38+
RefreshAndScanAll();
39+
}
40+
41+
private void OnGUI()
42+
{
43+
RenderTopToolBar();
44+
RenderResultsSection();
45+
}
46+
47+
private void RenderTopToolBar()
48+
{
49+
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
50+
51+
if (GUILayout.Button("Refresh & Scan Project", EditorStyles.toolbarButton))
52+
{
53+
RefreshAndScanAll();
54+
}
55+
56+
GUILayout.FlexibleSpace();
57+
58+
if (GUILayout.Button("Expand All", EditorStyles.toolbarButton))
59+
{
60+
SetEventUsagesExpandedState(true);
61+
}
62+
63+
if (GUILayout.Button("Collapse All", EditorStyles.toolbarButton))
64+
{
65+
SetEventUsagesExpandedState(false);
66+
}
67+
68+
EditorGUILayout.EndHorizontal();
69+
}
70+
71+
private void RenderResultsSection()
72+
{
73+
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
74+
75+
if (_allProjectEvents.Count == 0)
76+
{
77+
EditorGUILayout.HelpBox("No ScriptableObjects implementing ISOEventBase were found in the project.",
78+
MessageType.Info);
79+
}
80+
81+
foreach (var soEvent in _allProjectEvents)
82+
{
83+
RenderSOEventsUsages(soEvent);
84+
}
85+
86+
EditorGUILayout.EndScrollView();
87+
}
88+
89+
private void RenderSOEventsUsages(ScriptableObject soEvent)
90+
{
91+
// Default to expanded if state not found
92+
if (!_expandedStates.ContainsKey(soEvent))
93+
{
94+
_expandedStates[soEvent] = true;
95+
}
96+
97+
var expanded = _expandedStates[soEvent];
98+
_headerStyle ??= CreateHeaderStyle();
99+
100+
// Visual Feedback: Highlight background if expanded
101+
if (expanded)
102+
{
103+
GUI.backgroundColor = new Color(0.8f, 0.9f, 1f);
104+
}
105+
106+
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
107+
108+
RenderScriptableObjectEventEntryHeaderToggle(soEvent, expanded);
109+
GUI.backgroundColor = Color.white;
110+
111+
if (expanded)
112+
{
113+
EditorGUILayout.Space(2);
114+
if (_masterResults.ContainsKey(soEvent) && _masterResults[soEvent].Count > 0)
115+
{
116+
foreach (var assetEntry in _masterResults[soEvent])
117+
{
118+
// Group by Asset (Prefab/Scene)
119+
EditorGUILayout.BeginVertical(EditorStyles.textArea);
120+
121+
GUILayout.Label($"📂 {System.IO.Path.GetFileName(assetEntry.Key)}", EditorStyles.boldLabel);
122+
123+
foreach (var detail in assetEntry.Value)
124+
{
125+
RenderSOEventUsage(detail);
126+
}
127+
128+
EditorGUILayout.EndVertical();
129+
EditorGUILayout.Space(1);
130+
}
131+
}
132+
else
133+
{
134+
EditorGUILayout.LabelField(" No usages detected in prefabs or scenes.", EditorStyles.centeredGreyMiniLabel);
135+
}
136+
}
137+
138+
EditorGUILayout.EndVertical();
139+
EditorGUILayout.Space(2);
140+
}
141+
142+
private static GUIStyle CreateHeaderStyle()
143+
{
144+
var headerStyle = new GUIStyle(EditorStyles.miniButtonMid);
145+
headerStyle.alignment = TextAnchor.MiddleLeft;
146+
headerStyle.fontStyle = FontStyle.Bold;
147+
headerStyle.fontSize = 11;
148+
headerStyle.fixedHeight = 25;
149+
150+
return headerStyle;
151+
}
152+
153+
private void RenderScriptableObjectEventEntryHeaderToggle(ScriptableObject soEvent, bool expanded)
154+
{
155+
var arrow = expanded ? "▼" : "▶";
156+
if (GUILayout.Button($" {arrow} {soEvent.name.ToUpper()} [{soEvent.GetType().Name}]", _headerStyle))
157+
{
158+
_expandedStates[soEvent] = !expanded;
159+
}
160+
}
161+
162+
private static void RenderSOEventUsage(UsageDetail detail)
163+
{
164+
EditorGUILayout.BeginHorizontal();
165+
GUILayout.Space(15);
166+
// Deep link to component
167+
if (GUILayout.Button($" # GO: {detail.GameObjectName} ({detail.ComponentTypeName})", EditorStyles.label))
168+
{
169+
EditorGUIUtility.PingObject(detail.Context);
170+
}
171+
EditorGUILayout.EndHorizontal();
172+
}
173+
174+
private void SetEventUsagesExpandedState(bool expand)
175+
{
176+
var keys = _expandedStates.Keys.ToList();
177+
178+
foreach (var key in keys)
179+
{
180+
_expandedStates[key] = expand;
181+
}
182+
}
183+
184+
#region === Asset scanning logic ===
185+
186+
private void RefreshAndScanAll()
187+
{
188+
_allProjectEvents.Clear();
189+
_masterResults.Clear();
190+
191+
FindAllScriptableObjectEventAssets();
192+
ScanDependencies();
193+
}
194+
195+
private void FindAllScriptableObjectEventAssets()
196+
{
197+
var guids = AssetDatabase.FindAssets("t:ScriptableObject");
198+
199+
foreach (var guid in guids)
200+
{
201+
var path = AssetDatabase.GUIDToAssetPath(guid);
202+
var scriptableObjectAsset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);
203+
204+
if (scriptableObjectAsset is ISOEventBase)
205+
{
206+
_allProjectEvents.Add(scriptableObjectAsset);
207+
_masterResults[scriptableObjectAsset] = new Dictionary<string, List<UsageDetail>>();
208+
if (!_expandedStates.ContainsKey(scriptableObjectAsset))
209+
{
210+
_expandedStates[scriptableObjectAsset] = true;
211+
}
212+
}
213+
}
214+
}
215+
216+
private void ScanDependencies()
217+
{
218+
var potentialAssets = AssetDatabase.FindAssets("t:Prefab t:Scene");
219+
foreach (var guid in potentialAssets)
220+
{
221+
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
222+
var dependencies = AssetDatabase.GetDependencies(assetPath);
223+
224+
foreach (var soEvent in _allProjectEvents)
225+
{
226+
if (dependencies.Contains(AssetDatabase.GetAssetPath(soEvent)))
227+
{
228+
if (assetPath.EndsWith(PrefabExtension))
229+
{
230+
ScanPrefab(assetPath, soEvent);
231+
}
232+
else if (assetPath.EndsWith(SceneExtension))
233+
{
234+
ScanScene(assetPath, soEvent);
235+
}
236+
}
237+
}
238+
}
239+
}
240+
241+
private void ScanPrefab(string path, ScriptableObject target)
242+
{
243+
var root = AssetDatabase.LoadAssetAtPath<GameObject>(path);
244+
var allComponents = root.GetComponentsInChildren<Component>(true);
245+
CheckComponents(path, allComponents, target);
246+
}
247+
248+
private void ScanScene(string path, ScriptableObject target)
249+
{
250+
// Load scene additively and silently to inspect contents
251+
var tempScene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive);
252+
var allComponents = Resources.FindObjectsOfTypeAll<Component>().Where(c => c.gameObject.scene == tempScene);
253+
CheckComponents(path, allComponents, target);
254+
EditorSceneManager.CloseScene(tempScene, true);
255+
}
256+
257+
private void CheckComponents(string assetPath, IEnumerable<Component> components, ScriptableObject target)
258+
{
259+
foreach (var component in components)
260+
{
261+
if (component == null)
262+
{
263+
continue;
264+
}
265+
266+
// Iterate through all serialized properties to find the SO reference
267+
var serializedObject = new SerializedObject(component);
268+
var property = serializedObject.GetIterator();
269+
270+
while (property.NextVisible(true))
271+
{
272+
if (property.propertyType == SerializedPropertyType.ObjectReference && property.objectReferenceValue == target)
273+
{
274+
if (!_masterResults[target].ContainsKey(assetPath))
275+
{
276+
_masterResults[target][assetPath] = new List<UsageDetail>();
277+
}
278+
279+
_masterResults[target][assetPath].Add(new UsageDetail {
280+
GameObjectName = component.gameObject.name,
281+
ComponentTypeName = component.GetType().Name,
282+
Context = component
283+
});
284+
break;
285+
}
286+
}
287+
}
288+
}
289+
290+
#endregion
291+
}
292+
}

Editor/AuditorTool/EventSystemAuditor.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "com.espidigames.scriptable-object-event-system",
33
"displayName": "EG-Scriptable Object Event System",
44
"description": "Editor system that automatizes Scriptable Object Event creation",
5-
"version": "1.2.0",
5+
"version": "2.0.0",
66
"unity": "2021.3",
77
"repository": {
88
"type": "git",

0 commit comments

Comments
 (0)