Last active
November 10, 2023 03:52
-
-
Save Aryazaky/243e13b166736268035995fb421fb65c to your computer and use it in GitHub Desktop.
UnityEventValidator Class for Unity Events. Used to find <Missing> method references in UnityEvents (as they don't scream error).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
using Object = UnityEngine.Object; | |
namespace Aryazaky | |
{ | |
// How to use: | |
// Go to Tools > Unity Event Validator | |
// Press the buttons | |
/// <summary> | |
/// Utility class for detecting and reporting missing or invalid type references in Unity assets, | |
/// particularly in UnityEvents. It can also identify missing methods referenced in UnityEvents. | |
/// </summary> | |
public class UnityEventValidator : EditorWindow | |
{ | |
[MenuItem("Tools/Unity Event Validator")] | |
private static void ShowWindow() | |
{ | |
UnityEventValidator window = GetWindow<UnityEventValidator>(); | |
window.titleContent = new GUIContent("Unity Event Validator"); | |
window.Show(); | |
} | |
private void OnGUI() | |
{ | |
if (GUILayout.Button("Check Missing Arguments")) | |
{ | |
CheckMissingArguments(); | |
} | |
if (GUILayout.Button("Check Missing Methods")) | |
{ | |
CheckMissingMethods(); | |
} | |
} | |
private void CheckMissingArguments() | |
{ | |
string[] assetPaths = AssetDatabase.GetAllAssetPaths(); | |
List<(string typeName, string assetPath)> invalidTypes = new(); | |
foreach (string assetPath in assetPaths) | |
{ | |
// Check if the asset is in the Assets folder | |
if (!assetPath.StartsWith("Assets/")) | |
{ | |
continue; | |
} | |
if (assetPath.EndsWith(".asset") || assetPath.EndsWith(".prefab") || assetPath.EndsWith(".unity")) | |
{ | |
string assetContent = File.ReadAllText(assetPath); | |
MatchCollection matches = | |
Regex.Matches(assetContent, @"m_ObjectArgumentAssemblyTypeName:\s*([\w.]+)"); | |
foreach (Match match in matches) | |
{ | |
string typeName = match.Groups[1].Value; | |
if (typeName.Contains("UnityEngine.") || typeName.Contains("TMPro.") || | |
typeName.Contains("System.")) | |
{ | |
// Skips unity objects or classes from other assemblies. | |
// As there is no way to detect from which assembly they come from (or I don't know how). | |
// At least other assemblies doesn't need to be verified. | |
// The drawback is, this needs to keep being updated as more types from other assemblies are used. | |
continue; | |
} | |
// Currently only able to detect classes from the default Assembly-CSharp. | |
// The ", Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" is mandatory, I don't know why. | |
// ", Assembly-CSharp" alone are not enough. typeName alone aren't enough. | |
// Because of this, there is no way to check if a type from other assemblies are valid. | |
// Also, we can't change the assembly name or tidy up the project in multiple assemblies. Yet another drawback. | |
Type type = Type.GetType(typeName + | |
", Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); | |
if (type == null) | |
{ | |
invalidTypes.Add((typeName, assetPath)); | |
} | |
} | |
} | |
} | |
if (invalidTypes.Count > 0) | |
{ | |
string errorMessage = invalidTypes.Aggregate("Invalid Types found in assets:\n", | |
(current, invalidType) => current + $"{invalidType.typeName} in {invalidType.assetPath}\n"); | |
Debug.LogError(errorMessage); | |
} | |
else | |
{ | |
Debug.Log("No invalid types found in assets."); | |
} | |
} | |
private void CheckMissingMethods() | |
{ | |
string[] assetPaths = AssetDatabase.GetAllAssetPaths(); | |
List<(string className, string methodName, string assetPath)> missingMethods = new(); | |
foreach (string assetPath in assetPaths) | |
{ | |
// Check if the asset is in the Assets folder | |
if (!assetPath.StartsWith("Assets/")) | |
{ | |
continue; | |
} | |
if (assetPath.EndsWith(".asset") || assetPath.EndsWith(".prefab") || assetPath.EndsWith(".unity")) | |
{ | |
string assetContent = File.ReadAllText(assetPath); | |
MatchCollection typeMatches = Regex.Matches(assetContent, @"m_TargetAssemblyTypeName:\s*([\w.]*)(?:\r?\n|$|,)"); | |
MatchCollection methodMatches = Regex.Matches(assetContent, @"m_MethodName:\s*([\w]+)"); | |
List<string> classNames = typeMatches.Select(match => match.Groups[1].Value).ToList(); | |
List<string> methodNames = methodMatches.Select(match => match.Groups[1].Value).ToList(); | |
for (int i = 0; i < classNames.Count; i++) | |
{ | |
string className = classNames[i]; | |
string methodName = methodNames[i]; | |
if (className == "" || className.Contains("UnityEngine.")) | |
{ | |
//Debug.Log($"class name is empty in {assetPath}. Method is {methodName}"); | |
continue; | |
} | |
Type targetType = Type.GetType(className + | |
", Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); | |
if (targetType != null) | |
{ | |
// Get all methods of the class | |
MethodInfo[] methods = targetType.GetMethods(); | |
// Check if the desired method exists in the list of methods | |
bool methodExists = methods.Any(method => method.Name == methodName); | |
if (!methodExists) | |
{ | |
missingMethods.Add((className, methodName, assetPath)); | |
} | |
} | |
else | |
{ | |
// Handle the case where the target class type is not found. | |
Object obj = AssetDatabase.LoadAssetAtPath(assetPath, typeof(Object)); | |
Debug.Log($"Unrecognized class name {className} in {assetPath}. " + | |
$"Might be something that this tool has missed.", obj); | |
} | |
} | |
} | |
} | |
if (missingMethods.Count > 0) | |
{ | |
string errorMessage = missingMethods.Aggregate("Methods missing in assets:\n", | |
(current, missingMethod) => | |
current + | |
$"{missingMethod.className}.{missingMethod.methodName} in {missingMethod.assetPath}\n"); | |
Debug.LogError(errorMessage); | |
} | |
else | |
{ | |
Debug.Log("All methods are present in assets."); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment