├── Editor.meta ├── Editor ├── ColorfulLogBuilder.cs ├── ColorfulLogBuilder.cs.meta ├── Commands.cs ├── Commands.cs.meta ├── Settings.cs ├── Settings.cs.meta ├── SettingsWindow.cs └── SettingsWindow.cs.meta ├── IgnoreRefCheckerAttribute.cs ├── IgnoreRefCheckerAttribute.cs.meta ├── README.md ├── README.md.meta ├── Tests.meta └── Tests ├── RefCheckerTestComponent.cs └── RefCheckerTestComponent.cs.meta /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 792f2608d77e4d388a670ee8e1052e60 3 | timeCreated: 1494844644 -------------------------------------------------------------------------------- /Editor/ColorfulLogBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace UnityRefChecker 4 | { 5 | public class ColorfulLogBuilder 6 | { 7 | private StringBuilder log = new StringBuilder(); 8 | private bool colorful = true; 9 | 10 | public void SetColorful(bool use) { 11 | colorful = use; 12 | } 13 | 14 | public void StartColor() { 15 | if (colorful) { 16 | log.Append(""); 17 | } 18 | } 19 | 20 | public void EndColor() { 21 | if (colorful) { 22 | log.Append(""); 23 | } 24 | } 25 | 26 | public void Append(string text) { 27 | log.Append(text); 28 | } 29 | 30 | public override string ToString() { 31 | return log.ToString(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Editor/ColorfulLogBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2894bc57bdb95664088b557fa5dbde8f 3 | timeCreated: 1494682547 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/Commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using UnityRefCheckerExternal; 4 | using UnityEngine; 5 | using UnityEditor; 6 | using UnityEditor.Callbacks; 7 | using UnityEditor.SceneManagement; 8 | using UnityEngine.SceneManagement; 9 | 10 | namespace UnityRefChecker 11 | { 12 | public static class Commands 13 | { 14 | private static bool wasErrorInCheck = false; 15 | private static bool runningAfterCompilation = false; 16 | 17 | [DidReloadScripts] 18 | private static void RunAfterCompilation() { 19 | if (Settings.GetCheckOnCompilation()) { 20 | runningAfterCompilation = true; 21 | CheckBuildScenes(); 22 | runningAfterCompilation = false; 23 | } 24 | } 25 | 26 | public static void CheckBuildScenes() { 27 | EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo(); 28 | string previouslyOpenScenePath = SceneManager.GetActiveScene().path; 29 | 30 | EditorBuildSettingsScene[] buildSettingsScenes = EditorBuildSettings.scenes; 31 | for (int i = 0; i < buildSettingsScenes.Length; i++) { 32 | EditorBuildSettingsScene settingsScene = buildSettingsScenes[i]; 33 | Scene scene = GetSceneFromSettingsScene(settingsScene); 34 | CheckScene(scene); 35 | } 36 | EditorSceneManager.OpenScene(previouslyOpenScenePath); 37 | 38 | if (!wasErrorInCheck && !runningAfterCompilation) { 39 | Debug.Log("UnityRefChecker: All good!"); 40 | } 41 | wasErrorInCheck = false; 42 | } 43 | 44 | public static void CheckOpenScene() { 45 | EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo(); 46 | var scene = SceneManager.GetActiveScene(); 47 | CheckScene(scene); 48 | 49 | if (!wasErrorInCheck && !runningAfterCompilation) { 50 | Debug.Log("UnityRefChecker: All good!"); 51 | } 52 | wasErrorInCheck = false; 53 | } 54 | 55 | public static void ClearConsole() { 56 | Assembly assembly = Assembly.GetAssembly(typeof(SceneView)); 57 | Type logEntries = assembly.GetType("UnityEditorInternal.LogEntries"); 58 | MethodInfo clearConsoleMethod = logEntries.GetMethod("Clear"); 59 | clearConsoleMethod.Invoke(new object(), null); 60 | } 61 | 62 | private static Scene GetSceneFromSettingsScene(EditorBuildSettingsScene settingsScene) { 63 | string scenePath = settingsScene.path; 64 | EditorSceneManager.OpenScene(scenePath); 65 | Scene scene = SceneManager.GetSceneByPath(scenePath); 66 | return scene; 67 | } 68 | 69 | private static void CheckScene(Scene scene) { 70 | GameObject[] roots = scene.GetRootGameObjects(); 71 | for (int i = 0; i < roots.Length; i++) { 72 | CheckRootGameObject(roots[i]); 73 | } 74 | } 75 | 76 | private static void CheckRootGameObject(GameObject go) { 77 | var components = go.GetComponents(); 78 | for (int i = 0; i < components.Length; i++) { 79 | CheckComponent(components[i]); 80 | } 81 | } 82 | 83 | private static void CheckComponent(Component c) { 84 | // Ignore non-MonoBehaviours like Transform, Camera etc 85 | bool isBehaviour = c as MonoBehaviour; 86 | if (!isBehaviour) { 87 | return; 88 | } 89 | 90 | Type compType = c.GetType(); 91 | BindingFlags fieldTypes = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public; 92 | FieldInfo[] fields = compType.GetFields(fieldTypes); 93 | 94 | for (int i = 0; i < fields.Length; i++) { 95 | FieldInfo info = fields[i]; 96 | 97 | //Debug.Log("Field=" + info.Name + " type=" + info.MemberType); 98 | bool shouldPrintLog = ShouldPrintLogForComponent(c, info); 99 | 100 | if (shouldPrintLog) { 101 | BuildAndPrintLog(c, info); 102 | 103 | if (!wasErrorInCheck) { 104 | wasErrorInCheck = true; 105 | } 106 | } 107 | } 108 | } 109 | 110 | private static bool ShouldPrintLogForComponent(Component c, FieldInfo info) { 111 | object value = info.GetValue(c); 112 | bool isAssigned = value != null; 113 | 114 | bool hasIgnoreAttribute = FieldHasAttribute(info, typeof(IgnoreRefCheckerAttribute)); 115 | 116 | bool isSerializeable = info.IsPublic || FieldHasAttribute(info, typeof(SerializeField)); 117 | bool hiddenInInspector = FieldHasAttribute(info, typeof(HideInInspector)); 118 | 119 | bool shouldPrintLog = !isAssigned && !hasIgnoreAttribute && isSerializeable && !hiddenInInspector; 120 | return shouldPrintLog; 121 | } 122 | 123 | private static bool FieldHasAttribute(FieldInfo info, Type attributeType) { 124 | return info.GetCustomAttributes(attributeType, true).Length > 0; 125 | } 126 | 127 | private static string BuildLog(Component c, FieldInfo info) { 128 | var log = new ColorfulLogBuilder(); 129 | bool useColor = Settings.GetColorfulLogs(); 130 | log.SetColorful(useColor); 131 | log.Append("UnityRefChecker: Component "); 132 | log.StartColor(); 133 | log.Append(c.GetType().Name); 134 | log.EndColor(); 135 | log.Append(" has a null reference for field "); 136 | log.StartColor(); 137 | log.Append(info.Name); 138 | log.EndColor(); 139 | log.Append(" on GameObject "); 140 | log.StartColor(); 141 | log.Append(c.gameObject.name); 142 | log.EndColor(); 143 | log.Append(" in Scene "); 144 | log.StartColor(); 145 | log.Append(c.gameObject.scene.name); 146 | log.EndColor(); 147 | return log.ToString(); 148 | } 149 | 150 | private static void BuildAndPrintLog(Component c, FieldInfo info) { 151 | string log = BuildLog(c, info); 152 | Debug.logger.LogFormat(Settings.GetLogSeverity(), log); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /Editor/Commands.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f7723b2905e70654b858d8afe0f40c28 3 | timeCreated: 1494138631 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Editor/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace UnityRefChecker 5 | { 6 | public class Settings 7 | { 8 | public static bool GetCheckOnCompilation() { 9 | return GetPlayerPrefsBool(Keys.checkOnCompilation, false); 10 | } 11 | 12 | public static void SetCheckOnCompilation(bool check) { 13 | SetPlayerPrefsBool(Keys.checkOnCompilation, check); 14 | } 15 | 16 | public static LogType GetLogSeverity() { 17 | string savedStr = PlayerPrefs.GetString(Keys.logSeverity, "Error"); 18 | switch (savedStr) { 19 | case "Error": 20 | return LogType.Error; 21 | case "Assert": 22 | return LogType.Assert; 23 | case "Warning": 24 | return LogType.Warning; 25 | case "Log": 26 | return LogType.Log; 27 | case "Exception": 28 | return LogType.Exception; 29 | default: 30 | ClearSettings(); 31 | throw new IndexOutOfRangeException(); 32 | } 33 | } 34 | 35 | public static void SetLogSeverity(LogType type) { 36 | string typeStr = type.ToString(); 37 | PlayerPrefs.SetString(Keys.logSeverity, typeStr); 38 | } 39 | 40 | public static bool GetColorfulLogs() { 41 | return GetPlayerPrefsBool(Keys.colorfulLogs, true); 42 | } 43 | 44 | public static void SetColorfulLogs(bool colorful) { 45 | SetPlayerPrefsBool(Keys.colorfulLogs, colorful); 46 | } 47 | 48 | public static void ClearSettings() { 49 | PlayerPrefs.DeleteKey(Keys.checkOnCompilation); 50 | PlayerPrefs.DeleteKey(Keys.logSeverity); 51 | PlayerPrefs.DeleteKey(Keys.colorfulLogs); 52 | } 53 | 54 | private static bool GetPlayerPrefsBool(string key, bool defaultValue) { 55 | int defaultValueInt = defaultValue ? 1 : 0; 56 | return PlayerPrefs.GetInt(key, defaultValueInt) == 1; 57 | } 58 | 59 | private static void SetPlayerPrefsBool(string key, bool value) { 60 | int valueInt = value ? 1 : 0; 61 | PlayerPrefs.SetInt(key, valueInt); 62 | } 63 | 64 | private static class Keys 65 | { 66 | private static string refcheckerPretext = "RefChecker:"; 67 | public static string checkOnCompilation = refcheckerPretext + "checkOnCompilation"; 68 | public static string logSeverity = refcheckerPretext + "logSeverity"; 69 | public static string colorfulLogs = refcheckerPretext + "colorfulLogs"; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Editor/Settings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b53aa79450649189ec9351cc5d2d0cb 3 | timeCreated: 1494678239 -------------------------------------------------------------------------------- /Editor/SettingsWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | 4 | namespace UnityRefChecker 5 | { 6 | public class SettingsWindow : EditorWindow 7 | { 8 | private bool checkOnCompilation; 9 | private LogType logSeverity; 10 | private bool colorfulLogs; 11 | 12 | private const string checkAfterCompilationInfo = "Checks all build scenes whenever Unity finishes compiling."; 13 | 14 | [MenuItem("Window/UnityRefChecker")] 15 | public static void ShowWindow() { 16 | GetWindow(typeof(SettingsWindow)); 17 | } 18 | 19 | public void Awake() { 20 | checkOnCompilation = Settings.GetCheckOnCompilation(); 21 | logSeverity = Settings.GetLogSeverity(); 22 | colorfulLogs = Settings.GetColorfulLogs(); 23 | } 24 | 25 | private void OnGUI() { 26 | GUILayout.Label("UnityRefChecker", EditorStyles.boldLabel); 27 | DrawDocumentationButton(); 28 | 29 | GUILayout.Label("Commands", EditorStyles.boldLabel); 30 | DrawCommandButtons(); 31 | 32 | GUILayout.Label("Settings", EditorStyles.boldLabel); 33 | DrawSettings(); 34 | } 35 | 36 | private static void DrawDocumentationButton() { 37 | if (GUILayout.Button("Documentation")) { 38 | Application.OpenURL("https://github.com/haydenjameslee/unityrefchecker"); 39 | } 40 | } 41 | 42 | private static void DrawCommandButtons() { 43 | if (GUILayout.Button("Check All Build Scenes")) { 44 | Commands.CheckBuildScenes(); 45 | } 46 | if (GUILayout.Button("Check Open Scene")) { 47 | Commands.CheckOpenScene(); 48 | } 49 | } 50 | 51 | private void DrawSettings() { 52 | DrawCheckOnCompilationToggle(); 53 | DrawLogSeverityPopup(); 54 | DrawColorfulLogsToggle(); 55 | DrawResetSettingsButton(); 56 | DrawClearConsoleButton(); 57 | } 58 | 59 | private void DrawCheckOnCompilationToggle() { 60 | bool toggleValue = EditorGUILayout.Toggle("Check after compilation", checkOnCompilation); 61 | if (toggleValue) { 62 | EditorGUILayout.HelpBox(checkAfterCompilationInfo, MessageType.Info); 63 | } 64 | if (toggleValue != checkOnCompilation) { 65 | Settings.SetCheckOnCompilation(toggleValue); 66 | checkOnCompilation = toggleValue; 67 | } 68 | } 69 | 70 | private void DrawLogSeverityPopup() { 71 | LogType selectedLogSeverity = (LogType)EditorGUILayout.EnumPopup("Log type", logSeverity); 72 | if (selectedLogSeverity != logSeverity) { 73 | Settings.SetLogSeverity(selectedLogSeverity); 74 | logSeverity = selectedLogSeverity; 75 | } 76 | } 77 | 78 | private void DrawColorfulLogsToggle() { 79 | bool toggleValue = EditorGUILayout.Toggle("Colorful logs", colorfulLogs); 80 | if (toggleValue != colorfulLogs) { 81 | Settings.SetColorfulLogs(toggleValue); 82 | colorfulLogs = toggleValue; 83 | } 84 | } 85 | 86 | private void DrawResetSettingsButton() { 87 | if (GUILayout.Button("Reset Settings")) { 88 | Settings.ClearSettings(); 89 | Close(); 90 | } 91 | } 92 | 93 | private void DrawClearConsoleButton() { 94 | if (GUILayout.Button("Clear Console")) { 95 | Commands.ClearConsole(); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Editor/SettingsWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8ea20638869ca114ca8474fd5f30444f 3 | timeCreated: 1494676580 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /IgnoreRefCheckerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityRefCheckerExternal 4 | { 5 | public class IgnoreRefCheckerAttribute : Attribute 6 | { 7 | // 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /IgnoreRefCheckerAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a3354a741a2ad84f8fcb2800ab5b9bd 3 | timeCreated: 1494844002 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityRefChecker 2 | 3 | Unassigned reference warnings at compile time, across scenes. 4 | 5 | UnityRefChecker helps you avoid null references in MonoBehaviours by looking through all MonoBehaviour references in a scene and warning you in Unity's console if a reference has not been assigned. 6 | 7 | Fields that cause a log: 8 | - Unassigned public MonoBehaviour fields that do not have [IgnoreRefChecker] or [HideInInspector] 9 | - Unassigned private MonoBehaviour fields that do not have [IgnoreRefChecker] and do have [SerializeField] 10 | 11 | 12 | ## Example 13 | 14 | Here are some example logs: 15 | 16 | ![Example logs](http://i.imgur.com/qMypSw9.png "Example logs") 17 | 18 | 19 | ## Getting Started 20 | 21 | 1. Open your Unity project 22 | 23 | 2. Clone this project into the `Assets/` folder 24 | 25 | 3. Add the `[IgnoreRefChecker]` attribute in front of any members that you wish to keep unassigned 26 | 27 | 4. In Unity, go to `Window -> UnityRefChecker` to run commands and configure settings 28 | 29 | 30 | ## Commands 31 | 32 | - `Check All Build Scenes` - Checks all MonoBehaviour references in all scenes listed in the Unity Build Settings 33 | 34 | - `Check Open Scene` - Checks all MonoBehaviour references in the currently active scene 35 | 36 | 37 | ## Attributes 38 | 39 | - `IgnoreRefChecker` - Add this attribute to fields that you wish to keep unassigned. UnityRefChecker will not warn you about these fields 40 | 41 | 42 | ## Settings 43 | 44 | | Property | Description | Default Value | 45 | | -------- | ----------- | ------------- | 46 | | Check after compilation | Runs the `Check All Build Scenes` command every time Unity finishes compiling | false | 47 | | Log type | The severity of the log using Unity's LogType (Error, Log, Warning) | Error | 48 | | Colorful logs | Adds color to the Unity console logs to highlight important info | true | 49 | 50 | 51 | ## TODO 52 | 53 | - Make video tutorial showing the problem this solves 54 | - Include prefabs in checks 55 | - Add to Unity Asset Store 56 | 57 | 58 | ## Testing 59 | 60 | To test UnityRefChecker create a new Unity project, clone UnityRefChecker and set up a scene like this: 61 | 62 | ![Test scene](http://i.imgur.com/8TxyP84.png "Example test scene structure") 63 | 64 | Then open the UnityRefChecker Window and run commands. The RefCheckerTestComponent has the expected results as comments. 65 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a90624843e6ddff449748ef4944cc957 3 | timeCreated: 1494682849 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 39fab7a159bf41dbad958cad28dc022d 3 | timeCreated: 1494849580 -------------------------------------------------------------------------------- /Tests/RefCheckerTestComponent.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityRefCheckerExternal; 3 | 4 | public class RefCheckerTestComponent : MonoBehaviour 5 | { 6 | public int exampleInt; 7 | [IgnoreRefChecker] public MonoBehaviour exampleWithTag; 8 | public MonoBehaviour withoutReferenceWithoutTag; // Should print log 9 | public MonoBehaviour withoutReferenceWithoutTag2; // Should print log 10 | public MonoBehaviour withReference; 11 | 12 | private MonoBehaviour privateNonSerializable; 13 | [SerializeField] private MonoBehaviour privateSerializable; // Should print log 14 | 15 | [HideInInspector] public MonoBehaviour hiddenInInspector; 16 | } 17 | -------------------------------------------------------------------------------- /Tests/RefCheckerTestComponent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 497e68e164ec8e74b8b8b44b02a1ee2c 3 | timeCreated: 1494137187 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | --------------------------------------------------------------------------------