Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Created November 9, 2024 16:54
Show Gist options
  • Save adammyhre/a7c14b094ff2bdfb0a86df0579b4c539 to your computer and use it in GitHub Desktop.
Save adammyhre/a7c14b094ff2bdfb0a86df0579b4c539 to your computer and use it in GitHub Desktop.
Unity Hierarchy Icons
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;
}
}
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;
}
}
}
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