├── Docs~ ├── Images.meta └── Images │ ├── DemoFinal.gif │ ├── MainImage.jpg │ ├── PackageManager.jpg │ └── transform-changes-debugger-youtube-thumb.jpg ├── Editor.meta ├── Editor ├── BuildDefineSymbolManager.cs ├── BuildDefineSymbolManager.cs.meta ├── ConfigPersistableEditorWindow.cs ├── ConfigPersistableEditorWindow.cs.meta ├── EditorWindowPersistableConfiguration.cs ├── EditorWindowPersistableConfiguration.cs.meta ├── EventILWeaverCommandLineArgsGenerator.cs ├── EventILWeaverCommandLineArgsGenerator.cs.meta ├── LayoutHelper.cs ├── LayoutHelper.cs.meta ├── MissingUnityEventsManagerConfiguration.cs ├── MissingUnityEventsManagerConfiguration.cs.meta ├── MissingUnityEventsManagerEditorWindow.cs ├── MissingUnityEventsManagerEditorWindow.cs.meta └── Plugins~ │ └── EventILWeaver.Console.exe ├── LICENSE ├── LICENSE.meta ├── MissingUnityEvents.asmdef ├── MissingUnityEvents.asmdef.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /Docs~/Images.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a1b4b28d036481c9128764ef421ae93 3 | timeCreated: 1633335433 -------------------------------------------------------------------------------- /Docs~/Images/DemoFinal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handzlikchris/Unity.MissingUnityEvents/90049357ef6b6fda83ec288a188afb1e1548adf7/Docs~/Images/DemoFinal.gif -------------------------------------------------------------------------------- /Docs~/Images/MainImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handzlikchris/Unity.MissingUnityEvents/90049357ef6b6fda83ec288a188afb1e1548adf7/Docs~/Images/MainImage.jpg -------------------------------------------------------------------------------- /Docs~/Images/PackageManager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handzlikchris/Unity.MissingUnityEvents/90049357ef6b6fda83ec288a188afb1e1548adf7/Docs~/Images/PackageManager.jpg -------------------------------------------------------------------------------- /Docs~/Images/transform-changes-debugger-youtube-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handzlikchris/Unity.MissingUnityEvents/90049357ef6b6fda83ec288a188afb1e1548adf7/Docs~/Images/transform-changes-debugger-youtube-thumb.jpg -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdaec5a63144e614383c58bb582b834b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/BuildDefineSymbolManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEditor; 4 | 5 | namespace Assets.MissingUnityEvents.Editor 6 | { 7 | public static class BuildDefineSymbolManager 8 | { 9 | public static void SetBuildDefineSymbolState(string buildSymbol, bool isEnabled) 10 | { 11 | var allBuildSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup) 12 | .Split(';').ToList(); 13 | var isBuildSymbolDefined = allBuildSymbols.Any(s => s == buildSymbol); 14 | 15 | if (isEnabled && !isBuildSymbolDefined) 16 | { 17 | allBuildSymbols.Add(buildSymbol); 18 | SetBuildSymbols(allBuildSymbols); 19 | } 20 | 21 | if (!isEnabled && isBuildSymbolDefined) 22 | { 23 | allBuildSymbols.Remove(buildSymbol); 24 | SetBuildSymbols(allBuildSymbols); 25 | } 26 | } 27 | 28 | private static void SetBuildSymbols(List allBuildSymbols) 29 | { 30 | PlayerSettings.SetScriptingDefineSymbolsForGroup( 31 | EditorUserBuildSettings.selectedBuildTargetGroup, 32 | string.Join(";", allBuildSymbols.ToArray()) 33 | ); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Editor/BuildDefineSymbolManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 86bb4b335759fbf428964eb2bb589e1e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ConfigPersistableEditorWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Assets.MissingUnityEvents.Editor 6 | { 7 | public abstract class ConfigPersistableEditorWindow : EditorWindow where T: EditorWindowPersistableConfiguration, new() 8 | { 9 | protected T _config; 10 | private bool _stopShowingErrorMessageOnGuiError; 11 | 12 | protected abstract void OnGUIInternal(); 13 | 14 | void OnGUI() 15 | { 16 | if (_config == null) 17 | { 18 | _config = new T(); 19 | _config = _config.LoadConfig(); 20 | } 21 | 22 | try 23 | { 24 | OnGUIInternal(); 25 | } 26 | catch (Exception e) 27 | { 28 | if (!_stopShowingErrorMessageOnGuiError) 29 | { 30 | var result = EditorUtility.DisplayDialog("Error", 31 | $"There were error executing OnGUI, this could be caused by editor configuration being incorrectly persisted, do you want to recreate?", 32 | "Yes, recreate", "No and stop showing that message"); 33 | 34 | if (result) 35 | { 36 | RecreateConfig(); 37 | } 38 | else 39 | { 40 | _stopShowingErrorMessageOnGuiError = true; 41 | } 42 | } 43 | 44 | 45 | throw e; 46 | } 47 | 48 | if (GUI.changed) 49 | { 50 | _config.SaveChanges(); 51 | } 52 | } 53 | 54 | public void RemoveConfig() 55 | { 56 | _config = new T(); 57 | _config.RemoveFromPersistentStore(); 58 | } 59 | 60 | public void RecreateConfig() 61 | { 62 | _config = new T(); 63 | _config = _config.InitializeEmptyConfig(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Editor/ConfigPersistableEditorWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8399500eb1dc37d4ca80031fe2ca349f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/EditorWindowPersistableConfiguration.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Assets.MissingUnityEvents.Editor 5 | { 6 | public abstract class EditorWindowPersistableConfiguration where T: EditorWindowPersistableConfiguration, new() 7 | { 8 | private static string CurrentlyPersistedValue; 9 | private static readonly string EditorPrefsKey = $"EditorWindowPersistableConfiguration_{typeof(T).Name}"; 10 | 11 | protected abstract void InitEmpty(); 12 | 13 | public void SaveChanges() 14 | { 15 | var json = JsonUtility.ToJson(this); 16 | if (json != CurrentlyPersistedValue) 17 | { 18 | EditorPrefs.SetString(EditorPrefsKey, json); 19 | } 20 | } 21 | 22 | public T LoadConfig() 23 | { 24 | CurrentlyPersistedValue = EditorPrefs.GetString(EditorPrefsKey); 25 | if (!string.IsNullOrEmpty(CurrentlyPersistedValue)) 26 | { 27 | return JsonUtility.FromJson(CurrentlyPersistedValue); 28 | } 29 | else 30 | { 31 | return InitializeEmptyConfig(); 32 | } 33 | } 34 | 35 | public T InitializeEmptyConfig() 36 | { 37 | var settings = new T(); 38 | settings.InitEmpty(); 39 | settings.SaveChanges(); 40 | return settings; 41 | } 42 | 43 | public void RemoveFromPersistentStore() 44 | { 45 | EditorPrefs.DeleteKey(EditorPrefsKey); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Editor/EditorWindowPersistableConfiguration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d57ebbe71425e2846b4e106f93bdbf2e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/EventILWeaverCommandLineArgsGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Assets.MissingUnityEvents.Editor 9 | { 10 | public class EventILWeaverCommandLineArgsGenerator 11 | { 12 | private const string MultipleItemsForArgCommandLineDelimiter = ";"; 13 | 14 | public static string GenerateRevertToOriginalCommandLineArgs(MissingUnityEventsManagerConfiguration config) 15 | { 16 | return $"revert-to-original " + 17 | $"-t \"{string.Join(MultipleItemsForArgCommandLineDelimiter, GetUnityRequiredDllPaths(config, false))}\""; 18 | 19 | } 20 | 21 | public static string GenerateAddEventsCommandLineArgs(MissingUnityEventsManagerConfiguration config) 22 | { 23 | return $"add-events " + 24 | $"-t \"{string.Join(MultipleItemsForArgCommandLineDelimiter, GetUnityRequiredDllPaths(config, false))}\" " + 25 | $"--target-definitions {string.Join(MultipleItemsForArgCommandLineDelimiter, config.EventConfigurationEntries.Select(c => $"{c.ObjectType}-{c.PropertyName}-{c.DllName}"))}"; 26 | } 27 | 28 | public static string GenerateCreateHelperClassesArgs(MissingUnityEventsManagerConfiguration config) 29 | { 30 | return $"generate-helper-code " + 31 | $"-t \"{string.Join(MultipleItemsForArgCommandLineDelimiter, GetUnityRequiredDllPaths(config, true))}\" " + 32 | $"-o \"{config.HelperClassFilePath}\" " + 33 | $"-n \"{config.HelperClassNamespace}\" " + 34 | $"--using-statements System:UnityEngine " + 35 | $"--enabled-build-symbol {MissingUnityEventsManagerConfiguration.CustomAutoGeneratedEventsEnabledBuildSymbol} " + 36 | $"--include-custom-code-when-no-build-symbol \"{config.HelperClassIncludeCustomCodeWhenNoBuildSymbol.Replace("\"", "\\\"")}\""; 37 | } 38 | 39 | private static List GetUnityRequiredDllPaths(MissingUnityEventsManagerConfiguration config, bool singlePerDllType) 40 | { 41 | var unityRequiredDllPaths = new List(); 42 | 43 | var rootDir = new DirectoryInfo(UnityEditorDataDirectoryPath); 44 | var unityRequiredDlls = config.EventConfigurationEntries.Select(c => c.DllName).GroupBy(d => d).Select(g => g.First()); 45 | foreach (var unityRequiredDll in unityRequiredDlls) 46 | { 47 | RecursivelyFindDlls(rootDir, unityRequiredDll, unityRequiredDllPaths); 48 | } 49 | 50 | if (singlePerDllType) 51 | { 52 | unityRequiredDllPaths = unityRequiredDllPaths 53 | .GroupBy(f => f) 54 | .Select(g => g.First()) 55 | .ToList(); 56 | } 57 | 58 | return unityRequiredDllPaths; 59 | } 60 | 61 | private static void RecursivelyFindDlls(DirectoryInfo currentDir, string dllName, List unityRequiredDllPaths) 62 | { 63 | try 64 | { 65 | unityRequiredDllPaths.AddRange(currentDir.GetFiles(dllName + ".dll").Select(f => f.FullName).ToList()); 66 | var childDirectories = currentDir.GetDirectories(); 67 | foreach (var childDirectory in childDirectories) 68 | { 69 | RecursivelyFindDlls(childDirectory, dllName, unityRequiredDllPaths); 70 | } 71 | } 72 | catch (DirectoryNotFoundException e) 73 | { 74 | Debug.Log($"Path too long for {currentDir.FullName} if there are any matching DLLs ({dllName}) they'll be excluded. This is internal windows limitation - some workarounds can be found in 'https://forum.unity.com/threads/solved-directorynotfoundexception-with-different-packages.643297/'"); 75 | } 76 | catch (PathTooLongException e) 77 | { 78 | Debug.Log($"Path too long for {currentDir.FullName} if there are any matching DLLs ({dllName}) they'll be excluded. This is internal windows limitation - some workarounds can be found in 'https://forum.unity.com/threads/solved-directorynotfoundexception-with-different-packages.643297/'"); 79 | } 80 | catch (Exception e) 81 | { 82 | Debug.Log($"Unknown exception - this is usually issue with path too long for {currentDir.FullName} if there are any matching DLLs ({dllName}) they'll be excluded. This is internal windows limitation - some workarounds can be found in 'https://forum.unity.com/threads/solved-directorynotfoundexception-with-different-packages.643297/'"); 83 | } 84 | } 85 | 86 | private static string UnityEditorDataDirectoryPath => EditorApplication.applicationContentsPath; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Editor/EventILWeaverCommandLineArgsGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: be10dd76e27539249a17301ac2eec2f0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/LayoutHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Assets.MissingUnityEvents.Editor 6 | { 7 | public static class LayoutHelper 8 | { 9 | private class HorizontalDisposable: IDisposable 10 | { 11 | public void Dispose() 12 | { 13 | EditorGUILayout.EndHorizontal(); 14 | } 15 | } 16 | 17 | public static IDisposable Horizontal() 18 | { 19 | EditorGUILayout.BeginHorizontal(); 20 | return new HorizontalDisposable(); 21 | } 22 | 23 | 24 | private class LabelWidthDisposable : IDisposable 25 | { 26 | private int _widthPxBeforeChange; 27 | 28 | public LabelWidthDisposable(int widthPxBeforeChange) 29 | { 30 | _widthPxBeforeChange = widthPxBeforeChange; 31 | } 32 | 33 | public void Dispose() 34 | { 35 | EditorGUIUtility.labelWidth = _widthPxBeforeChange; 36 | } 37 | } 38 | 39 | public static IDisposable LabelWidth(int widthPx) 40 | { 41 | var labelWidthDisposable = new LabelWidthDisposable((int)EditorGUIUtility.labelWidth); 42 | EditorGUIUtility.labelWidth = widthPx; 43 | 44 | return labelWidthDisposable; 45 | } 46 | 47 | public static void TextBoxWithButton(Func getValue, Action setValue, Action onButtonClick, string labelText, string buttonText = "Pick") 48 | { 49 | using (Horizontal()) 50 | { 51 | var current = EditorGUILayout.TextField(labelText, getValue()); 52 | setValue(current); 53 | if (GUILayout.Button(buttonText, GUILayout.Width(40))) 54 | { 55 | onButtonClick(); 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Editor/LayoutHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 94daeba163ff896429416046cfd46f74 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/MissingUnityEventsManagerConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace Assets.MissingUnityEvents.Editor 7 | { 8 | [Serializable] 9 | public class EventConfiguration 10 | { 11 | public string ObjectType; 12 | public string PropertyName; 13 | public string DllName; 14 | 15 | public EventConfiguration(string objectType, string propertyName, string dllName) 16 | { 17 | ObjectType = objectType; 18 | PropertyName = propertyName; 19 | DllName = dllName; 20 | } 21 | 22 | public static EventConfiguration New() 23 | { 24 | return new EventConfiguration("", "", string.Empty); 25 | } 26 | } 27 | 28 | public class MissingUnityEventsManagerConfiguration : EditorWindowPersistableConfiguration 29 | { 30 | public const string UnityCoreModuleDllName = "UnityEngine.CoreModule"; 31 | public const string UnityPhysicsModuleDllName = "UnityEngine.PhysicsModule"; 32 | 33 | public const string CustomAutoGeneratedEventsEnabledBuildSymbol = "ILWeavedEventsOn"; 34 | private const string IlWeaverPluginExeName = "EventILWeaver.Console"; 35 | 36 | public string IlWeaverPluginExecutablePath; 37 | public string HelperClassFilePath; 38 | public string HelperClassNamespace; 39 | public bool HelperClassForceUseNoBuildSymbolInEditor; 40 | public string HelperClassIncludeCustomCodeWhenNoBuildSymbol = $"Debug.LogWarning(\"Build symbol {CustomAutoGeneratedEventsEnabledBuildSymbol} not specified.\");"; 41 | 42 | public List EventConfigurationEntries = new List(); 43 | 44 | protected override void InitEmpty() 45 | { 46 | EventConfigurationEntries = new List 47 | { 48 | new EventConfiguration(typeof(Transform).Name, nameof(Transform.position), UnityCoreModuleDllName), 49 | new EventConfiguration(typeof(Transform).Name, nameof(Transform.localScale), UnityCoreModuleDllName), 50 | new EventConfiguration(typeof(Transform).Name, nameof(Transform.rotation), UnityCoreModuleDllName), 51 | new EventConfiguration(typeof(BoxCollider).Name, nameof(BoxCollider.name), UnityPhysicsModuleDllName), 52 | }; 53 | } 54 | 55 | public string GetIlWeaverPluginFolder() 56 | { 57 | var thisFilePath = AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(EditorWindow.GetWindow())); 58 | var ilWeaverPluginFolder = thisFilePath.Substring(0, thisFilePath.LastIndexOf("/")) + "/Plugins~"; 59 | return ilWeaverPluginFolder; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Editor/MissingUnityEventsManagerConfiguration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c0b403992537f9843ae1a16aa22bb2fe 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/MissingUnityEventsManagerEditorWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using UnityEditor; 7 | using UnityEngine; 8 | using Debug = UnityEngine.Debug; 9 | using File = UnityEngine.Windows.File; 10 | 11 | namespace Assets.MissingUnityEvents.Editor 12 | { 13 | public class MissingUnityEventsManagerEditorWindow : ConfigPersistableEditorWindow 14 | { 15 | private const string PluginExecutablePathLabel = "Plugin Executable Path"; 16 | 17 | private int _windowWidthPx; 18 | 19 | [MenuItem("Tools/Missing Unity Events/Run")] 20 | public static void ExecuteShowWindowMenuAction() 21 | { 22 | GetWindow(false, "Missing Unity Events"); 23 | } 24 | 25 | [MenuItem("Tools/Missing Unity Events/Recreate Config")] 26 | public static void ExecuteRecreateConfigMenuAction() 27 | { 28 | var editor = GetWindow(false, "Missing Unity Events"); 29 | editor.RecreateConfig(); 30 | } 31 | 32 | //[MenuItem("Missing Unity Events/Remove Config")] 33 | //public static void ExecuteRemoveConfigMenuAction() 34 | //{ 35 | // var editor = GetWindow(false, "Missing Unity Events"); 36 | // editor.RemoveConfig(); 37 | //} 38 | 39 | private object _typeToPropertiesMapGeneratingLock = new object(); 40 | private Dictionary> _typeToPropertiesMap; 41 | public Dictionary> TypeToPropertiesMap 42 | { 43 | get 44 | { 45 | if (_typeToPropertiesMap == null) 46 | { 47 | _typeToPropertiesMap = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("Unity")).ToList() 48 | .SelectMany(a => a.GetTypes()).ToDictionary(t => t, 49 | t => t.GetProperties(BindingFlags.Public 50 | | BindingFlags.Instance 51 | | BindingFlags.DeclaredOnly) 52 | .Where(p => p.CanRead && p.CanWrite 53 | && p.GetMethod?.MethodImplementationFlags == MethodImplAttributes.Managed && 54 | p.SetMethod?.MethodImplementationFlags == MethodImplAttributes.Managed).ToList() 55 | ); 56 | } 57 | return _typeToPropertiesMap; 58 | } 59 | } 60 | 61 | void Awake() 62 | { 63 | } 64 | 65 | private string GetAssemblyName(Assembly assy) 66 | { 67 | return assy.FullName.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)[0]; 68 | } 69 | 70 | protected override void OnGUIInternal() 71 | { 72 | _windowWidthPx = (int)position.width; 73 | 74 | GUILayout.Label("Plugin Configuration", EditorStyles.boldLabel); 75 | 76 | LayoutHelper.TextBoxWithButton(() => _config.IlWeaverPluginExecutablePath, (v) => _config.IlWeaverPluginExecutablePath = v, SelectPluginViaPicker, PluginExecutablePathLabel); 77 | GUILayout.Space(10); 78 | 79 | GUILayout.Label("Event Configuration", EditorStyles.boldLabel); 80 | 81 | EditorGUILayout.SelectableLabel("Event addition is quite flexible, you can add more targets based on type, property name and " + 82 | "property type, plugin will try to weave those.", EditorStyles.textArea); 83 | GUILayout.Space(10); 84 | 85 | CreateTargetDefinitionEditor(); 86 | 87 | if (GUILayout.Button("Add New Event", GUILayout.Width(100))) _config.EventConfigurationEntries.Add(EventConfiguration.New()); 88 | 89 | if (GUILayout.Button("Weave Events to DLL", GUILayout.Height(40))) 90 | { 91 | ExecuteWeaveEventsToDll(); 92 | } 93 | if (GUILayout.Button("Revert DLL to original", GUILayout.Height(40))) 94 | { 95 | ExecuteRevertDllToOriginal(); 96 | } 97 | GUILayout.Space(10); 98 | 99 | 100 | GUILayout.Label("Helper classes", EditorStyles.boldLabel); 101 | EditorGUILayout.SelectableLabel("It's best to abstract usage of weaved events. This way if you're using clean dll " + 102 | "you won't have to change your existing code, instead it'll just log warning if specified.", EditorStyles.textArea); 103 | 104 | LayoutHelper.TextBoxWithButton(() => _config.HelperClassFilePath, (v) => _config.HelperClassFilePath = v, SelectHelperFileViaPicker, "Helper Class File Path"); 105 | _config.HelperClassNamespace = EditorGUILayout.TextField("Namespace", _config.HelperClassNamespace); 106 | _config.HelperClassIncludeCustomCodeWhenNoBuildSymbol = EditorGUILayout.TextField("Custom Code When no Build Symbol", _config.HelperClassIncludeCustomCodeWhenNoBuildSymbol); 107 | 108 | using (LayoutHelper.LabelWidth(200)) _config.HelperClassForceUseNoBuildSymbolInEditor = EditorGUILayout.Toggle("Force no build symbol (in editor)", _config.HelperClassForceUseNoBuildSymbolInEditor); 109 | 110 | BuildDefineSymbolManager.SetBuildDefineSymbolState(MissingUnityEventsManagerConfiguration.CustomAutoGeneratedEventsEnabledBuildSymbol, !_config.HelperClassForceUseNoBuildSymbolInEditor); 111 | 112 | if (GUILayout.Button("Generate Helper Classes", GUILayout.Height(40))) 113 | { 114 | ExecuteGenerateHelperClasses(); 115 | } 116 | GUILayout.Space(10); 117 | } 118 | 119 | private void EnsureEventConfigEntriesHaveCorrectDllModuleHydrated() 120 | { 121 | foreach (var eventConfigEventConfigurationEntry in _config.EventConfigurationEntries) 122 | { 123 | eventConfigEventConfigurationEntry.DllName = GetAssemblyName( 124 | TypeToPropertiesMap.First(t => t.Key.Name == eventConfigEventConfigurationEntry.ObjectType).Key.Assembly 125 | ); 126 | } 127 | } 128 | 129 | private void CreateTargetDefinitionEditor() 130 | { 131 | using (LayoutHelper.Horizontal()) 132 | { 133 | GUILayout.Label("Type", PercentageWidth(40)); 134 | GUILayout.Label("Property Name", PercentageWidth(40)); 135 | } 136 | 137 | for (var i = _config.EventConfigurationEntries.Count - 1; i >= 0; i--) 138 | { 139 | var eventConfigurationEntry = _config.EventConfigurationEntries[i]; 140 | using (LayoutHelper.Horizontal()) 141 | { 142 | eventConfigurationEntry.ObjectType = 143 | EditorGUILayout.TextField("", eventConfigurationEntry.ObjectType, PercentageWidth(40)); 144 | 145 | var typeProperties = 146 | TypeToPropertiesMap.FirstOrDefault(kv => kv.Key.Name == eventConfigurationEntry.ObjectType); 147 | if (typeProperties.Key != null) 148 | { 149 | var selectedTypePropertyIndex = -1; 150 | var allTypeProperties = typeProperties.Value.Select(p => p.Name).ToArray(); 151 | for (var j = 0; j < allTypeProperties.Length; j++) 152 | { 153 | if (allTypeProperties[j] == eventConfigurationEntry.PropertyName) 154 | { 155 | selectedTypePropertyIndex = j; 156 | break; 157 | } 158 | } 159 | 160 | selectedTypePropertyIndex = 161 | EditorGUILayout.Popup(selectedTypePropertyIndex, allTypeProperties, PercentageWidth(40)); 162 | if (selectedTypePropertyIndex != -1) 163 | { 164 | eventConfigurationEntry.PropertyName = allTypeProperties[selectedTypePropertyIndex]; 165 | } 166 | } 167 | else 168 | { 169 | EditorGUILayout.TextField("", "", PercentageWidth(40)); 170 | } 171 | 172 | if (GUILayout.Button("-", PercentageWidth(8))) 173 | { 174 | _config.EventConfigurationEntries.RemoveAt(i); 175 | } 176 | } 177 | } 178 | } 179 | 180 | public GUILayoutOption PercentageWidth(int percentageWidth) 181 | { 182 | var pxWidth = percentageWidth / 100f * _windowWidthPx; 183 | return GUILayout.Width(pxWidth); 184 | } 185 | 186 | private void ExecuteGenerateHelperClasses() 187 | { 188 | if (string.IsNullOrWhiteSpace(_config.HelperClassNamespace)) 189 | { 190 | EditorUtility.DisplayDialog("Error", $"Namespace is required", "Ok"); 191 | } 192 | else 193 | { 194 | EnsureEventConfigEntriesHaveCorrectDllModuleHydrated(); 195 | RunWeavingExecutable(EventILWeaverCommandLineArgsGenerator.GenerateCreateHelperClassesArgs(_config), false); 196 | } 197 | } 198 | 199 | private void ExecuteRevertDllToOriginal() 200 | { 201 | EnsurePluginSelected(); 202 | EnsureEventConfigEntriesHaveCorrectDllModuleHydrated(); 203 | RunWeavingExecutable(EventILWeaverCommandLineArgsGenerator.GenerateRevertToOriginalCommandLineArgs(_config), true); 204 | _config.HelperClassForceUseNoBuildSymbolInEditor = true; 205 | } 206 | 207 | private void ExecuteWeaveEventsToDll() 208 | { 209 | EnsurePluginSelected(); 210 | 211 | EnsureEventConfigEntriesHaveCorrectDllModuleHydrated(); 212 | RunWeavingExecutable(EventILWeaverCommandLineArgsGenerator.GenerateAddEventsCommandLineArgs(_config), true); 213 | 214 | _config.HelperClassForceUseNoBuildSymbolInEditor = false; 215 | 216 | var result = EditorUtility.DisplayDialog("Warning", 217 | $"Continue in console window, you'll likely need to close Editor for weaving to complete.\r\n\r\n" + 218 | $"Weaving events for some types/properties (eg. Object.name) will crash Editor, it's possible more type/property combinations will cause similar issues.\r\n\r\n " + 219 | $"A rollback script will be created and kept in same folder as plugin.\r\n" + 220 | $" '{_config.IlWeaverPluginExecutablePath}'\r\n\r\n" + 221 | $"If any issues occur run it to revert to backups.", "Close editor", "Keep open"); 222 | 223 | if (result) 224 | { 225 | EditorApplication.Exit(0); 226 | } 227 | } 228 | 229 | private void EnsurePluginSelected() 230 | { 231 | if (!File.Exists(_config.IlWeaverPluginExecutablePath)) 232 | { 233 | EditorUtility.DisplayDialog("Error", 234 | $"Unable to find plugin, please make sure that '{PluginExecutablePathLabel}' is correct.", "Pick file"); 235 | SelectPluginViaPicker(); 236 | } 237 | } 238 | 239 | private void RunWeavingExecutable(string commandLineArgs, bool runAsAdministrator) 240 | { 241 | Process proc = new Process(); 242 | proc.StartInfo.FileName = _config.IlWeaverPluginExecutablePath; 243 | proc.StartInfo.UseShellExecute = true; 244 | if (runAsAdministrator) proc.StartInfo.Verb = "runas"; 245 | Debug.Log($"Running weaving DLL with arguments: '{commandLineArgs}'"); 246 | proc.StartInfo.Arguments = commandLineArgs; 247 | proc.Start(); 248 | } 249 | 250 | private void SelectPluginViaPicker() 251 | { 252 | _config.IlWeaverPluginExecutablePath = EditorUtility.OpenFilePanel("Select Plugin exe", _config.GetIlWeaverPluginFolder(), "exe"); 253 | } 254 | 255 | private void SelectHelperFileViaPicker() 256 | { 257 | _config.HelperClassFilePath = EditorUtility.OpenFilePanel("Select Helper class file (create empty if needed)", Application.dataPath, "cs"); 258 | } 259 | 260 | 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /Editor/MissingUnityEventsManagerEditorWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0f9a3dd30d5227c48b1ba9a51e712932 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Plugins~/EventILWeaver.Console.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handzlikchris/Unity.MissingUnityEvents/90049357ef6b6fda83ec288a188afb1e1548adf7/Editor/Plugins~/EventILWeaver.Console.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Chris Handzlik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92e3066203ad90e44a1976e89244ae81 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /MissingUnityEvents.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MissingUnityEvents", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [ 6 | "Android", 7 | "iOS", 8 | "LinuxStandalone64", 9 | "Lumin", 10 | "macOSStandalone", 11 | "PS4", 12 | "Stadia", 13 | "Switch", 14 | "tvOS", 15 | "WSA", 16 | "WebGL", 17 | "WindowsStandalone32", 18 | "WindowsStandalone64", 19 | "XboxOne" 20 | ], 21 | "allowUnsafeCode": false, 22 | "overrideReferences": false, 23 | "precompiledReferences": [], 24 | "autoReferenced": true, 25 | "defineConstraints": [], 26 | "versionDefines": [], 27 | "noEngineReferences": false 28 | } -------------------------------------------------------------------------------- /MissingUnityEvents.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c7797c4a5cb6eb84897c4c9f56fdcaa9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Missing Unity Events](Docs~/Images/MainImage.jpg) 2 | 3 | # Missing Unity Events 4 | 5 | If you were ever looking for a way to write more event driven code in Unity you probably found that it's not so easy at least not with some very basic structures like `Transforms`. 6 | 7 | To get it working you probably needed to abstract some parts of API and provide events there which would require buy-in from other devs as well as your own self-discipline to be properly used. 8 | 9 | Events directly in property setters come in handy in some situations: 10 | - Debugging a sneaky `Transform` that changes position/rotation/scale and you have no idea why 11 | - could be even worse if it's down to some third party plugin that modifies it in which case it gets even tricier 12 | - Writing more event driven code that's not tied to update 13 | 14 | Unfortunately those are simply not there, if you want to respond to some changes you'll likely going to use `Update` loop of some game object. 15 | 16 | > # Making This Tool Better - Visual Transform Changes Debugger 17 | > I've went further with this concept and created a tool that focuses on tracking ANY changes made to ANY transforms which is then neatly laid out in friendly GUI. Check it out at: 18 | > https://immersiveVRTools.com/projects/transform-changes-debugger 19 | > 20 | > [![Visual Transform Changes Debugger](Docs~/Images/transform-changes-debugger-youtube-thumb.jpg)](https://youtu.be/6YjrgHpE2x4 "Play") 21 | > *features showcase video* 22 | 23 | 24 | # Adding Events back to Unity APIs 25 | This tool can add those events to Unity DLLs enabling you to use them as you would any other events. It's very straight forward to use and works globally, which means once you've added them they'll stay there. 26 | 27 | ![Missing Unity Events Demo](Docs~/Images/DemoFinal.gif) 28 | 29 | ## Approach 30 | Tool uses `IL Weaving` and will add specified `Events` directly to Unity DLLs, both for `Editor` and `Builds`. These are standard events, nothing different from writing them out by hand. Usage and performance will be in line with what you'd expect from manually created events. 31 | 32 | > **Since DLL IL code is modified you want to check with your Unity License if that's allowed. Tool and docs are provided for educational purposes. Tool can be found at https://github.com/handzlikchris/AddingEventsToCompiled3rdPartyLibrary** 33 | 34 | ## Usage 35 | 36 | ### Weaving Events into DLLs 37 | 38 | 1) Go to `Window -> Package Manager` and `'Add package from hit URL...'` 39 | 40 | ![Package Manager](Docs~/Images/PackageManager.jpg) 41 | 42 | 2) use url : 'https://github.com/handzlikchris/Unity.MissingUnityEvents.git' 43 | 3) Click on the `Missing Unity Events` menu bar followed by `Run` 44 | 4) Pick weaver executable in via `Pick` next to `Plugin Executable Path` - it should alredy open in correct folder (if not it'll be located int the `Plugin~` folder, eg `/MissingUnityEvents/Assets/Plugins/MissingUnityEvents/Editor/Plugins~/EventILWeaver.Console.exe`) 45 | 5) Specify types that you want to add events to 46 | - once you type in valid type `PropertyName` dropdown will allow you to choose from likely supported properties 47 | 6) Click `Weave Events to DLL` 48 | 49 | ### Generating helper classes 50 | It's best to abstract usage of weaved events. This way if you're using clean dll you won't have to change your existing code, instead it'll just log warning if specified. 51 | 52 | 1) Click `Pick` next to `Helper Class File Path` and select the file that'll be used as a target for generated code 53 | - if it does not exist just create empty 54 | 2) Fill `Namespace` ideally this will be root namespace for your project, with that extensions method will be easily picked up 55 | 3) You can also specify `Custom Code When no Build Symbols` - this will be injected to method body when there build symbol is not defined (eg. DLLs were reverted and using new fields would result in error) 56 | 57 | ``` 58 | #if ILWeavedEventsOn 59 | public static class BoxColliderExtensions 60 | { 61 | 62 | public static void BindSetSizeExecuting(this BoxCollider obj, EventHandler handler) 63 | { 64 | obj.SetSizeExecuting += handler; 65 | } 66 | } 67 | #else 68 | public static class BoxColliderExtensions 69 | { 70 | 71 | public static void BindSetSizeExecuting(this BoxCollider obj, EventHandler handler) 72 | { 73 | 74 | } 75 | } 76 | #endif 77 | ``` 78 | 79 | 4) Click `Generate Helper Classes` 80 | 81 | #### Using helper classes 82 | From now on instead of using weaved `Events` eg. 83 | ``` BoxCollider.SetSizeExecuting += (sender, args) => { }; ``` 84 | 85 | You can use helper methods that'll not break your code with not modified DLLs 86 | ``` BoxCollider.BindSetSizeExecuting((sender, args) => { }); ``` 87 | 88 | #### Force no build symbol 89 | If you want to use helper classes with fallback code tick `Force no build symbol (in editor)` - this will turn the build symbol off (effectively turning off your binding). **The DLLs still has event fields in them**. 90 | 91 | ### Reverting to backup 92 | If you want to completely remove changes made by plugin click `'Revert DLL to original'` this will restore backups. 93 | 94 | ### Emergency backup restore 95 | In some cases changes made to DLLs will cause editor to crash. As example if you try to add event to `Object.name`. Potentially more types / properties will cause this issue, if that happens there's a rollback script created `revert-last-weaving.bat` in the same folder as plugin. Running it will revert to backups. -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c18222e219f37d4598d6cb1a4787817 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "missingunityevents", 3 | "displayName": "Missing Unity Events", 4 | "version": "1.0.0", 5 | "unity": "2018.3", 6 | "description": "Editor extension to generate events that are executed before property set, eg. Transform-position, Transform-rotation, Transform-parent which allows to write more event driven code or debug property changes that are not simple to track down. Easily customisable to allow adding events to different property setters / types." 7 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2df49bce3652eb419d4fe871f337b1d 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------