Created
November 9, 2024 16:54
-
-
Save adammyhre/a7c14b094ff2bdfb0a86df0579b4c539 to your computer and use it in GitHub Desktop.
Unity Hierarchy Icons
This file contains hidden or 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 UnityEditor; | |
using UnityEngine; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
[InitializeOnLoad] | |
public static class HierarchyIconDrawer { | |
static readonly Texture2D requiredIcon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/_Project/Art/RequiredIcon.png"); | |
static readonly Dictionary<Type, FieldInfo[]> cachedFieldInfo = new (); | |
static HierarchyIconDrawer() { | |
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemOnGUI; | |
} | |
static void OnHierarchyWindowItemOnGUI(int instanceID, Rect selectionRect) { | |
if (EditorUtility.InstanceIDToObject(instanceID) is not GameObject gameObject) return; | |
// Use GetComponentsInChildren to include components on children | |
foreach (var component in gameObject.GetComponents<Component>()) { | |
if (component == null) continue; | |
var fields = GetCachedFieldsWithRequiredAttribute(component.GetType()); | |
if (fields == null) continue; | |
if (fields.Any(field => IsFieldUnassigned(field.GetValue(component)))) { | |
var iconRect = new Rect(selectionRect.xMax - 20, selectionRect.y, 16, 16); | |
GUI.Label(iconRect, new GUIContent(requiredIcon, "One or more required fields are missing or empty.")); | |
break; | |
} | |
} | |
} | |
static FieldInfo[] GetCachedFieldsWithRequiredAttribute(Type componentType) { | |
if (!cachedFieldInfo.TryGetValue(componentType, out FieldInfo[] fields)) { | |
fields = componentType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); | |
List<FieldInfo> requiredFields = new (); | |
foreach (FieldInfo field in fields) { | |
bool isSerialized = field.IsPublic || field.IsDefined(typeof(SerializeField), false); | |
bool isRequired = field.IsDefined(typeof(RequiredFieldAttribute), false); | |
if (isSerialized && isRequired) { | |
requiredFields.Add(field); | |
} | |
} | |
fields = requiredFields.ToArray(); | |
cachedFieldInfo[componentType] = fields; | |
} | |
return fields; | |
} | |
static bool IsFieldUnassigned(object fieldValue) { | |
if (fieldValue == null) return true; | |
if (fieldValue is string stringValue && string.IsNullOrEmpty(stringValue)) return true; | |
if (fieldValue is System.Collections.IEnumerable enumerable) { | |
foreach (var item in enumerable) { | |
if (item == null) return true; | |
} | |
} | |
return false; | |
} | |
} |
This file contains hidden or 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 UnityEditor; | |
using UnityEngine; | |
[CustomPropertyDrawer(typeof(RequiredFieldAttribute))] | |
public class RequiredFieldDrawer : PropertyDrawer { | |
Texture2D requiredIcon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/_Project/Art/RequiredIcon.png"); | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { | |
EditorGUI.BeginProperty(position, label, property); | |
EditorGUI.BeginChangeCheck(); | |
Rect fieldRect = new (position.x, position.y, position.width - 20, position.height); | |
EditorGUI.PropertyField(fieldRect, property, label); | |
// If the field is required but unassigned, show the icon | |
if (IsFieldUnassigned(property)) { | |
Rect iconRect = new (position.xMax - 18, fieldRect.y, 16, 16); | |
GUI.Label(iconRect, new GUIContent(requiredIcon, "This field is required and is either missing or empty!")); | |
} | |
if (EditorGUI.EndChangeCheck()) { | |
property.serializedObject.ApplyModifiedProperties(); | |
EditorUtility.SetDirty(property.serializedObject.targetObject); | |
// Force a repaint of the hierarchy | |
EditorApplication.RepaintHierarchyWindow(); | |
} | |
EditorGUI.EndProperty(); | |
} | |
bool IsFieldUnassigned(SerializedProperty property) { | |
switch (property.propertyType) { | |
// Add additional types as necessary | |
case SerializedPropertyType.ObjectReference when property.objectReferenceValue: | |
case SerializedPropertyType.ExposedReference when property.exposedReferenceValue: | |
case SerializedPropertyType.AnimationCurve when property.animationCurveValue is { length: > 0 }: | |
case SerializedPropertyType.String when !string.IsNullOrEmpty( property.stringValue ): | |
return false; | |
default: | |
return true; | |
} | |
} | |
} |
This file contains hidden or 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 System; | |
using UnityEngine; | |
[AttributeUsage(AttributeTargets.Field)] | |
public class RequiredFieldAttribute : PropertyAttribute { } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment