diff --git a/Editor/Objects/ValidatorList.cs b/Editor/Objects/ValidatorList.cs index d75a061..0f498fa 100644 --- a/Editor/Objects/ValidatorList.cs +++ b/Editor/Objects/ValidatorList.cs @@ -10,6 +10,7 @@ namespace Module.ProjectValidator.Editor { private readonly Dictionary _attributeValidators = new(); private readonly Dictionary> _componentValidators = new(); + public readonly List GameObjectValidators = new(); public void AddAttribute(Type type) { @@ -59,6 +60,22 @@ namespace Module.ProjectValidator.Editor } } + public void AddGameObject(Type type) + { + if (type.IsInterface || type.IsAbstract) + return; + + try + { + var instance = (IGameObjectValidator)FormatterServices.GetUninitializedObject(type); + GameObjectValidators.Add(instance); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + public bool TryGetAttributeValidator(Type type, out object validatorInstance) { return _attributeValidators.TryGetValue(type, out validatorInstance); diff --git a/Editor/Utilities/ProjectValidatorUtility.cs b/Editor/Utilities/ProjectValidatorUtility.cs index c1f0c2f..92eb95b 100644 --- a/Editor/Utilities/ProjectValidatorUtility.cs +++ b/Editor/Utilities/ProjectValidatorUtility.cs @@ -38,14 +38,14 @@ namespace Module.ProjectValidator.Editor return window; } - internal static string GetAttributeShortName(Attribute attribute) + internal static string GetGameObjectValidatorName(IGameObjectValidator validator) { - var str = attribute.GetType().Name; - str = str.Replace("Attribute", string.Empty); + var str = validator.GetType().Name; + str = str.Replace("GameObjectValidator", string.Empty); str = ObjectNames.NicifyVariableName(str); return str; } - + internal static string GetComponentValidatorShortName(object obj) { var str = obj.GetType().Name; @@ -53,7 +53,15 @@ namespace Module.ProjectValidator.Editor str = ObjectNames.NicifyVariableName(str); return str; } - + + internal static string GetAttributeShortName(Attribute attribute) + { + var str = attribute.GetType().Name; + str = str.Replace("Attribute", string.Empty); + str = ObjectNames.NicifyVariableName(str); + return str; + } + internal static void AppendToScenePath(GameObject gameObject, ref string scenePath) { scenePath = string.IsNullOrEmpty(scenePath) ? gameObject.name : $"{scenePath}/{gameObject.name}"; diff --git a/Editor/ValidatorRunner.cs b/Editor/ValidatorRunner.cs index af9cdd4..f992f35 100644 --- a/Editor/ValidatorRunner.cs +++ b/Editor/ValidatorRunner.cs @@ -40,7 +40,7 @@ namespace Module.ProjectValidator.Editor ProjectValidatorUtility.OpenWindow(); stopwatch.Stop(); - Debug.Log(stopwatch.Elapsed.TotalMilliseconds + "ms"); + Debug.Log($"Validator took {stopwatch.Elapsed.TotalMilliseconds}ms"); return true; } @@ -63,9 +63,10 @@ namespace Module.ProjectValidator.Editor _validatorList = new ValidatorList(); _typeTree = new TypeTree(); - - FetchAllAttributeValidators(); + + FetchAllGameObjectValidators(); FetchAllComponentValidators(); + FetchAllAttributeValidators(); FetchAllTypesWithValidators(assemblies); FetchAllTypesWithValidators(assemblies); _initialized = true; @@ -100,6 +101,16 @@ namespace Module.ProjectValidator.Editor _validatorList.AddAttribute(types[i]); } } + + private static void FetchAllGameObjectValidators() + { + var types = TypeCache.GetTypesDerivedFrom(typeof(IGameObjectValidator)); + + for (var i = 0; i < types.Count; i++) + { + _validatorList.AddGameObject(types[i]); + } + } private static void FetchAllComponentValidators() { @@ -188,19 +199,39 @@ namespace Module.ProjectValidator.Editor private static void ValidateGameObject(GameObject gameObject, string scenePath, Report report) { ProjectValidatorUtility.AppendToScenePath(gameObject, ref scenePath); - ValidateComponents(gameObject, scenePath, report); + + var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject); + using var _ = ListPool.Get(out var results); + + for (var i = 0; i < _validatorList.GameObjectValidators.Count; i++) + { + results.Clear(); + + _validatorList.GameObjectValidators[i].Validate(gameObject, results); + var type = ProjectValidatorUtility.GetGameObjectValidatorName(_validatorList.GameObjectValidators[i]); + + for (var j = 0; j < results.Count; j++) + { + var result = results[j]; + + if (result.Severity != EValidatorSeverity.Valid) + report.Add(assetGuid, scenePath, string.Empty, type, result.Severity, result.Message); + } + } + + ValidateComponents(gameObject, assetGuid, scenePath, report); ValidateChildren(gameObject, scenePath, report); } - private static void ValidateComponents(GameObject gameObject, string scenePath, Report report) + private static void ValidateComponents(GameObject gameObject, GUID assetGuid, string scenePath, Report report) { using var _ = ListPool.Get(out var components); - var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject); gameObject.GetComponents(components); for (var i = 0; i < components.Count; i++) { - Validate(assetGuid, scenePath, components[i], report); + if (components[i] != null) + Validate(assetGuid, scenePath, components[i], report); } } diff --git a/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs b/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs new file mode 100644 index 0000000..ca2d231 --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class ComponentValidatorSkinnedMeshRenderer : IComponentValidator + { + public void Validate(SkinnedMeshRenderer component, List results) + { + var materials = component.sharedMaterials; + + for (var i = 0; i < materials.Length; i++) + { + if (materials[i] == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Missing material in slot #{i}")); + } + + if (component.sharedMesh == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh")); + } + } +} diff --git a/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs.meta b/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs.meta new file mode 100644 index 0000000..1080734 --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1610561c53a0aa84aaa903e5dde29694 diff --git a/Editor/Validators/GameObject.meta b/Editor/Validators/GameObject.meta new file mode 100644 index 0000000..736bf79 --- /dev/null +++ b/Editor/Validators/GameObject.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 45fa253dcfa349d5b1e9bd56ebac7c98 +timeCreated: 1779133809 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs b/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs new file mode 100644 index 0000000..8cd9270 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class GameObjectValidatorBrokenPrefab : IGameObjectValidator + { + public void Validate(GameObject gameObject, List results) + { + if (PrefabUtility.IsPrefabAssetMissing(gameObject)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "GameObject is missing prefab asset")); + } + } +} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs.meta new file mode 100644 index 0000000..d2ad050 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8fd1dfcd3d564622a918e2175499318d +timeCreated: 1779133864 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs b/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs new file mode 100644 index 0000000..dd43633 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class GameObjectValidatorMissingComponents : IGameObjectValidator + { + public void Validate(GameObject gameObject, List results) + { + var count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject); + + if (count != 0) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"GameObject is missing {count} component(s)")); + } + } +} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs.meta new file mode 100644 index 0000000..47d826d --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 66cbc43729ec4e81a9e353e537b3ccf0 +timeCreated: 1779133947 \ No newline at end of file diff --git a/README.md b/README.md index 6fd6b0f..5690ed5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,19 @@ A tool to help validate data across scenes, prefabs and scriptable objects. ![Hierachy Window](~Images/editor-hierarchy-window.png) ![Project Window](~Images/editor-project-window.png) +## Game Object Validators + +```csharp +public sealed class GameObjectValidatorBrokenPrefab : IGameObjectValidator +{ + public void Validate(GameObject gameObject, List results) + { + if (PrefabUtility.IsPrefabAssetMissing(gameObject)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "GameObject is missing prefab asset")); + } +} +``` + ## Component Validators ```csharp diff --git a/Runtime/Interfaces/IGameObjectValidator.cs b/Runtime/Interfaces/IGameObjectValidator.cs new file mode 100644 index 0000000..a2dab7a --- /dev/null +++ b/Runtime/Interfaces/IGameObjectValidator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Module.ProjectValidator +{ + public interface IGameObjectValidator + { + void Validate(GameObject gameObject, List results); + } +} \ No newline at end of file diff --git a/Runtime/Interfaces/IGameObjectValidator.cs.meta b/Runtime/Interfaces/IGameObjectValidator.cs.meta new file mode 100644 index 0000000..714cd2e --- /dev/null +++ b/Runtime/Interfaces/IGameObjectValidator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f7513a02611842029edd675cb64989ad +timeCreated: 1779133097 \ No newline at end of file diff --git a/package.json b/package.json index 279cf49..a9663df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.module.project-validator", - "version": "0.2.0", + "version": "0.3.0", "displayName": "Module.ProjectValidator", "description": "", "unity": "6000.3", diff --git a/~Images/editor-window.png b/~Images/editor-window.png index a9966dc..386ec41 100644 Binary files a/~Images/editor-window.png and b/~Images/editor-window.png differ