Added GameObject Validators

This commit is contained in:
Anders Ejlersen 2026-05-18 21:59:59 +02:00
parent c8a6815316
commit 269789b36f
15 changed files with 160 additions and 13 deletions

View file

@ -10,6 +10,7 @@ namespace Module.ProjectValidator.Editor
{ {
private readonly Dictionary<Type, object> _attributeValidators = new(); private readonly Dictionary<Type, object> _attributeValidators = new();
private readonly Dictionary<Type, List<object>> _componentValidators = new(); private readonly Dictionary<Type, List<object>> _componentValidators = new();
public readonly List<IGameObjectValidator> GameObjectValidators = new();
public void AddAttribute(Type type) 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) public bool TryGetAttributeValidator(Type type, out object validatorInstance)
{ {
return _attributeValidators.TryGetValue(type, out validatorInstance); return _attributeValidators.TryGetValue(type, out validatorInstance);

View file

@ -38,14 +38,14 @@ namespace Module.ProjectValidator.Editor
return window; return window;
} }
internal static string GetAttributeShortName(Attribute attribute) internal static string GetGameObjectValidatorName(IGameObjectValidator validator)
{ {
var str = attribute.GetType().Name; var str = validator.GetType().Name;
str = str.Replace("Attribute", string.Empty); str = str.Replace("GameObjectValidator", string.Empty);
str = ObjectNames.NicifyVariableName(str); str = ObjectNames.NicifyVariableName(str);
return str; return str;
} }
internal static string GetComponentValidatorShortName(object obj) internal static string GetComponentValidatorShortName(object obj)
{ {
var str = obj.GetType().Name; var str = obj.GetType().Name;
@ -53,7 +53,15 @@ namespace Module.ProjectValidator.Editor
str = ObjectNames.NicifyVariableName(str); str = ObjectNames.NicifyVariableName(str);
return 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) internal static void AppendToScenePath(GameObject gameObject, ref string scenePath)
{ {
scenePath = string.IsNullOrEmpty(scenePath) ? gameObject.name : $"{scenePath}/{gameObject.name}"; scenePath = string.IsNullOrEmpty(scenePath) ? gameObject.name : $"{scenePath}/{gameObject.name}";

View file

@ -40,7 +40,7 @@ namespace Module.ProjectValidator.Editor
ProjectValidatorUtility.OpenWindow(); ProjectValidatorUtility.OpenWindow();
stopwatch.Stop(); stopwatch.Stop();
Debug.Log(stopwatch.Elapsed.TotalMilliseconds + "ms"); Debug.Log($"Validator took {stopwatch.Elapsed.TotalMilliseconds}ms");
return true; return true;
} }
@ -63,9 +63,10 @@ namespace Module.ProjectValidator.Editor
_validatorList = new ValidatorList(); _validatorList = new ValidatorList();
_typeTree = new TypeTree(); _typeTree = new TypeTree();
FetchAllAttributeValidators(); FetchAllGameObjectValidators();
FetchAllComponentValidators(); FetchAllComponentValidators();
FetchAllAttributeValidators();
FetchAllTypesWithValidators<Component>(assemblies); FetchAllTypesWithValidators<Component>(assemblies);
FetchAllTypesWithValidators<ScriptableObject>(assemblies); FetchAllTypesWithValidators<ScriptableObject>(assemblies);
_initialized = true; _initialized = true;
@ -100,6 +101,16 @@ namespace Module.ProjectValidator.Editor
_validatorList.AddAttribute(types[i]); _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() private static void FetchAllComponentValidators()
{ {
@ -188,19 +199,39 @@ namespace Module.ProjectValidator.Editor
private static void ValidateGameObject(GameObject gameObject, string scenePath, Report report) private static void ValidateGameObject(GameObject gameObject, string scenePath, Report report)
{ {
ProjectValidatorUtility.AppendToScenePath(gameObject, ref scenePath); ProjectValidatorUtility.AppendToScenePath(gameObject, ref scenePath);
ValidateComponents(gameObject, scenePath, report);
var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject);
using var _ = ListPool<ValidatorResult>.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); 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<Component>.Get(out var components); using var _ = ListPool<Component>.Get(out var components);
var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject);
gameObject.GetComponents(components); gameObject.GetComponents(components);
for (var i = 0; i < components.Count; i++) for (var i = 0; i < components.Count; i++)
{ {
Validate(assetGuid, scenePath, components[i], report); if (components[i] != null)
Validate(assetGuid, scenePath, components[i], report);
} }
} }

View file

@ -0,0 +1,22 @@
using System.Collections.Generic;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class ComponentValidatorSkinnedMeshRenderer : IComponentValidator<SkinnedMeshRenderer>
{
public void Validate(SkinnedMeshRenderer component, List<ValidatorResult> 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"));
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1610561c53a0aa84aaa903e5dde29694

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45fa253dcfa349d5b1e9bd56ebac7c98
timeCreated: 1779133809

View file

@ -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<ValidatorResult> results)
{
if (PrefabUtility.IsPrefabAssetMissing(gameObject))
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "GameObject is missing prefab asset"));
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8fd1dfcd3d564622a918e2175499318d
timeCreated: 1779133864

View file

@ -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<ValidatorResult> results)
{
var count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
if (count != 0)
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"GameObject is missing {count} component(s)"));
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 66cbc43729ec4e81a9e353e537b3ccf0
timeCreated: 1779133947

View file

@ -9,6 +9,19 @@ A tool to help validate data across scenes, prefabs and scriptable objects.
![Hierachy Window](~Images/editor-hierarchy-window.png) ![Hierachy Window](~Images/editor-hierarchy-window.png)
![Project Window](~Images/editor-project-window.png) ![Project Window](~Images/editor-project-window.png)
## Game Object Validators
```csharp
public sealed class GameObjectValidatorBrokenPrefab : IGameObjectValidator
{
public void Validate(GameObject gameObject, List<ValidatorResult> results)
{
if (PrefabUtility.IsPrefabAssetMissing(gameObject))
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "GameObject is missing prefab asset"));
}
}
```
## Component Validators ## Component Validators
```csharp ```csharp

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using UnityEngine;
namespace Module.ProjectValidator
{
public interface IGameObjectValidator
{
void Validate(GameObject gameObject, List<ValidatorResult> results);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f7513a02611842029edd675cb64989ad
timeCreated: 1779133097

View file

@ -1,6 +1,6 @@
{ {
"name": "com.module.project-validator", "name": "com.module.project-validator",
"version": "0.2.0", "version": "0.3.0",
"displayName": "Module.ProjectValidator", "displayName": "Module.ProjectValidator",
"description": "", "description": "",
"unity": "6000.3", "unity": "6000.3",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before After
Before After