Created
May 5, 2022 10:19
-
-
Save Lachee/614cc67e1599fdc45fcbbb2feb5e740a to your computer and use it in GitHub Desktop.
The [Auto] will automatically link components to the script
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; | |
namespace Lachee.Utilities | |
{ | |
/// <summary> | |
/// Automatically fetches attached components | |
/// </summary> | |
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)] | |
public class AutoAttribute : PropertyAttribute | |
{ | |
/// <summary> | |
/// Hides the field from the inspector if the value is set. | |
/// </summary> | |
public bool Hidden { get; set; } = true; | |
/// <summary> | |
/// Search the tree for children with the same game object. | |
/// <para>If this attribute is on an array, it will populate the values from only children</para> | |
/// <para>If this attribute is not on an array, it will only check if the component cannot be found on the parent.</para> | |
/// </summary> | |
public bool IncludeChildren { get; set; } = true; | |
/// <summary> | |
/// Tag the children objects must have to be included. Leave blank or null for any tag. | |
/// </summary> | |
public string IncludeChildrenWithTag { get; set; } = ""; | |
} | |
} |
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 System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using UnityEditor; | |
using UnityEngine; | |
namespace Lachee.Utilities.Editor | |
{ | |
[CustomPropertyDrawer(typeof(AutoAttribute))] | |
public class AutoAttributeDrawer : PropertyDrawer | |
{ | |
const string PREF_ALWAYS_SCAN = "autoattr_scan"; | |
public static dynamic FindComponent(SerializedProperty property, AutoAttribute options) | |
{ | |
var baseComponent = property.serializedObject.targetObject as Component; | |
if (baseComponent == null) | |
throw new System.InvalidOperationException("Cannot find a component on a non-component object"); | |
var componentType = GetSerializedPropertyType(property); | |
if (componentType.IsArray) | |
{ | |
// Validate it is an array of components | |
if (!typeof(Component).IsAssignableFrom(componentType.GetElementType())) | |
throw new System.InvalidOperationException("Type is not a componet and cannot be looked up"); | |
if (options.IncludeChildren) | |
return baseComponent.GetComponentsInChildren(componentType.GetElementType()); | |
return baseComponent.GetComponents(componentType.GetElementType()); | |
} | |
else | |
{ | |
// Validate it is a component | |
if (!typeof(Component).IsAssignableFrom(componentType)) | |
throw new System.InvalidOperationException("Type is not a componet and cannot be looked up"); | |
var component = baseComponent.GetComponent(componentType); | |
if (component != null) | |
return component; | |
if (options.IncludeChildren) | |
return baseComponent.GetComponentsInChildren(componentType); | |
} | |
return null; | |
} | |
/// <summary>Searches and applies components for the serialized proeprty</summary> | |
public static bool ApplyToSerialziedProperty(SerializedProperty property, AutoAttribute attribute) { | |
var component = FindComponent(property, attribute); | |
if (component is Object comObject) | |
{ | |
property.objectReferenceValue = comObject; | |
return true; | |
} | |
else if (component is Object[] comArray) | |
{ | |
if (!property.isArray) | |
throw new System.InvalidOperationException("Cannot pass array to non-array field"); | |
property.ClearArray(); | |
property.arraySize = comArray.Length; | |
for (int i = 0; i < comArray.Length; i++) | |
{ | |
var element = property.GetArrayElementAtIndex(i); | |
element.objectReferenceValue = comArray[i]; | |
} | |
return comArray.Length > 0; | |
} | |
return false; | |
} | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
var attr = attribute as AutoAttribute; | |
// Null, so lets assign it | |
if (EditorPrefs.GetBool(PREF_ALWAYS_SCAN, false)) { | |
if (property.objectReferenceValue == null || (property.isArray && property.arraySize == 0)) | |
ApplyToSerialziedProperty(property, attr); | |
} | |
if (attr.Hidden) | |
{ | |
// Hidden but we cannot find one! | |
if (ShouldShow(property)) | |
{ | |
if (property.objectReferenceValue == null) | |
{ | |
GUI.color = Color.red; | |
label.tooltip = $"[BAD COMPONENT] " + label.tooltip; | |
} | |
EditorGUI.PropertyField(position, property, label); | |
GUI.color = Color.white; | |
} | |
} | |
else | |
{ | |
// Not hidden, lets just display it | |
GUI.color = Color.gray; | |
label.tooltip = $"[Automatically Fetched] " + label.tooltip; | |
EditorGUI.PropertyField(position, property, label); | |
GUI.color = Color.white; | |
} | |
} | |
private bool ShouldShow(SerializedProperty property) | |
{ | |
var attr = attribute as AutoAttribute; | |
if (!attr.Hidden) | |
return true; | |
if (property.objectReferenceValue == null) | |
return true; | |
var componentReferenceValue = property.objectReferenceValue as Component; | |
if (componentReferenceValue.gameObject != (property.serializedObject.targetObject as Component).gameObject) | |
return true; | |
return false; | |
} | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
return !ShouldShow(property) ? 0 : base.GetPropertyHeight(property, label); | |
} | |
private static System.Type GetSerializedPropertyType(SerializedProperty property) | |
{ | |
System.Type parentType = property.serializedObject.targetObject.GetType(); | |
System.Reflection.FieldInfo fi = parentType.GetField(property.propertyPath); | |
return fi.FieldType; | |
} | |
} | |
/// <summary>Observes the Auto-Attribute to rebuild lists.</summary> | |
[InitializeOnLoad] | |
public static class AutoAttributeObserver | |
{ | |
static AutoAttributeObserver() | |
{ | |
EditorApplication.hierarchyChanged += OnHierarchyChanged; | |
} | |
private static void OnHierarchyChanged() | |
{ | |
// Scan every MonoBehaviour for Auto Attribute fields | |
var processQueue = new Dictionary<Object, List<SearchRequest>>(); | |
var components = Resources.FindObjectsOfTypeAll(typeof(MonoBehaviour)); // Use MonoBehaviour as a slightly more optimised search | |
foreach(var component in components) { | |
bool addedComponentToProcess = false; | |
var fields = component.GetType().GetFields(); | |
foreach(var field in fields) { | |
var attributes = field.GetCustomAttributes(typeof(AutoAttribute), true); | |
if (attributes.Length > 0) { | |
if (!addedComponentToProcess) { | |
addedComponentToProcess = true; | |
processQueue.Add(component, new List<SearchRequest>(fields.Length)); | |
} | |
// While the attribuite doesnt actually support multiple on one field, | |
// we do this just to future proof. Performance is neglegitable. | |
foreach(var attribute in attributes) { | |
processQueue[component].Add(new SearchRequest() { | |
propertyPath = field.Name, | |
attribute = (AutoAttribute) attribute | |
}); | |
} | |
} | |
} | |
} | |
// Now process all the components | |
// We are going to create a serialized object then call the ApplyToSerialized the drawer has to all the | |
// appropriate properites of that serialized object | |
foreach(var kp in processQueue) { | |
bool hasMadeChanges = false; | |
var serializedObject = new SerializedObject(kp.Key); | |
foreach(var request in kp.Value) { | |
var property = serializedObject.FindProperty(request.propertyPath); | |
if (AutoAttributeDrawer.ApplyToSerialziedProperty(property, request.attribute)) | |
hasMadeChanges = true; | |
} | |
if (hasMadeChanges) | |
serializedObject.ApplyModifiedProperties(); | |
} | |
// Done | |
} | |
struct SearchRequest { | |
public string propertyPath; | |
public AutoAttribute attribute; | |
} | |
} | |
} |
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 Lachee.Utilities; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class Example : MonoBehaviour | |
{ | |
// This will automatically scan for all Box Colliders within our children | |
[Auto(Hidden = false)] | |
public BoxCollider[] scripts; | |
// This will automatically link the rigidbody | |
[Auto] | |
public new Rigidbody rigidbody; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment