Skip to content

Instantly share code, notes, and snippets.

@codeimpossible
Forked from LotteMakesStuff/AutohookAttribute.cs
Last active October 1, 2019 04:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codeimpossible/e7629352059441ffd7cd20e212e098b8 to your computer and use it in GitHub Desktop.
Save codeimpossible/e7629352059441ffd7cd20e212e098b8 to your computer and use it in GitHub Desktop.
[Autohook] property drawer for unity - Add this [Autohook] attribute to a property to and the inspector will automagically hook up a valid reference for you if it can find a component attached to the same game object that matches the field you put it on. You can watch a demo of this in action here https://youtu.be/faVt09NGzws <3

heres a tiny little example of how to use it

public class AutohookTest : MonoBehaviour
{
    [Autohook] // <-- will match Rigidbody on the current gameobject
    public Rigidbody rigidbody;
    
    [Autohook(AutohookSearchArea.Children)] // <-- will search children for first AudioSource
    public AudioSource audio;
    
    // Update is called once per frame
    void Update()
    {
        // do something
        rigidbody.AddForce(Vector3.up);
    }
}

The AutoHook attribute was originally coded by LotteMakesStuff. If you find it usefule you should support them on ko-fi!

// NOTE DONT put in an editor folder!
using UnityEngine;
public enum AutohookSearchArea {
Self,
Parent,
Children
}
public class AutohookAttribute : PropertyAttribute
{
public AutohookSearchArea SearchArea { get; private set; }
public AutohookAttribute(AutohookSearchArea searchArea = AutohookSearchArea.Self) {
SearchArea = searchArea;
}
}
// NOTE put in a Editor folder
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(AutohookAttribute))]
public class AutohookPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// First, lets attempt to find a valid component we could hook into this property
var component = FindAutohookTarget(property);
if (component != null)
{
// if we found something, AND the autohook is empty, lets slot it.
// the reason were straight up looking for a target component is so we
// can skip drawing the field if theres a valid autohook.
// this just looks a bit cleaner but isnt particularly safe. YMMV
if (property.objectReferenceValue == null)
property.objectReferenceValue = component;
return;
}
// havent found one? lets just draw the default property field, let the user manually
// hook something in.
EditorGUI.PropertyField(position, property, label);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// if theres a valid autohook target we skip drawing, so height is zeroed
var component = FindAutohookTarget(property);
if (component != null)
return 0;
// otherwise, return its default height (which should be the standard 16px unity usually uses)
return base.GetPropertyHeight(property, label);
}
/// <summary>
/// Takes a SerializedProperty and finds a local component that can be slotted into it.
/// Local in this context means its a component attached to the same GameObject.
/// This could easily be changed to use GetComponentInParent/GetComponentInChildren
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private Component FindAutohookTarget(SerializedProperty property)
{
var root = property.serializedObject;
var attribute = PropertyUtility.GetAttribute<AutohookAttribute>(property);
if (root.targetObject is Component)
{
// first, lets find the type of component were trying to autohook...
var type = GetTypeFromProperty(property);
// ...then use GetComponent(type) to see if there is one on our object.
var component = (Component)root.targetObject;
switch (attribute.SearchArea)
{
case AutohookSearchArea.Self: return component.GetComponent(type);
case AutohookSearchArea.Parent: return component.GetComponentInParent(type);
case AutohookSearchArea.Children: return component.GetComponentInChildren(type);
}
}
else
{
Debug.Log("OH NO handle fails here better pls");
}
return null;
}
/// <summary>
/// Uses reflection to get the type from a serialized property
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private static System.Type GetTypeFromProperty(SerializedProperty property)
{
// first, lets get the Type of component this serialized property is part of...
var parentComponentType = property.serializedObject.targetObject.GetType();
// ... then, using reflection well get the raw field info of the property this
// SerializedProperty represents...
var fieldInfo = parentComponentType.GetField(property.propertyPath);
// ... using that we can return the raw .net type!
return fieldInfo.FieldType;
}
/// <summary>
/// Uses reflection to get the attribute from a serialized property
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
public static T GetAttribute<T>(SerializedProperty property) where T : Attribute
{
FieldInfo fieldInfo = ReflectionUtility.GetField(GetTargetObject(property), property.name);
T[] attributes = (T[])fieldInfo.GetCustomAttributes(typeof(T), true);
return attributes.Length > 0 ? attributes[0] : null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment