├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------