Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Automatically reference components on same object, child, or parent

Discussion in 'Scripting' started by olejuer, Apr 7, 2020.

  1. olejuer

    olejuer

    Joined:
    Dec 1, 2014
    Posts:
    210
    Hi all,

    I am working with Unity for so many years now and I love optimizing workflows. It always bothered me that referencing components is either very manual (with public/serialized fields) , lots of boilerplate (with lazy properties) or inefficient (with GetComponent calls at runtime). Currently, I have lots of references to scripts on the same object or on a child. I wrote a small utility attribute that streamlines this.

    Code (CSharp):
    1. public class CustomMonoBehaviour : MonoBehaviour
    2. {
    3.     [ComponentReference(true, false)] public ComponentType _component;
    4. }
    The property drawer will call GetComponent to retrieve the object of the appropriate type. It has two optional bool parameters to search in children and/or parent game objects. Of course this has a limited use case, but I like it, so I figured I share it. Use it/share it as you please.

    Code (CSharp):
    1. public class ComponentReferenceAttribute : PropertyAttribute
    2. {
    3.     // ReSharper disable twice UnusedParameter.Local
    4.     public ComponentReferenceAttribute(bool findInChildren = false, bool findInParent = false)
    5.     { }
    6. }
    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(ComponentReferenceAttribute))]
    2. public class ComponentReferenceDrawer : PropertyDrawer
    3. {
    4.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    5.     {
    6.         CustomAttributeData customAttributeData = fieldInfo.CustomAttributes
    7.             .FirstOrDefault(a => a.AttributeType == typeof(ComponentReferenceAttribute));
    8.         if (customAttributeData == null)
    9.         {
    10.             EditorGUI.HelpBox(position, "Missing attribute data", MessageType.Warning);
    11.             return;
    12.         }
    13.  
    14.         var targetObject = property.serializedObject.targetObject as Component;
    15.         if (targetObject == null)
    16.         {
    17.             EditorGUI.HelpBox(position, "Invalid serialized object", MessageType.Warning);
    18.             return;
    19.         }
    20.  
    21.         var searchInChildren = (bool) customAttributeData.ConstructorArguments[0].Value;
    22.         var searchInParent = (bool) customAttributeData.ConstructorArguments[1].Value;
    23.  
    24.         var component = property.objectReferenceValue as Component;
    25.         if (component == null) component = targetObject.GetComponent(fieldInfo.FieldType);
    26.         if (component == null && searchInChildren)
    27.             component = targetObject.GetComponentInChildren(fieldInfo.FieldType);
    28.         if (component == null && searchInParent)
    29.             component = targetObject.GetComponentInParent(fieldInfo.FieldType);
    30.  
    31.         if (component != null)
    32.         {
    33.             property.objectReferenceValue = component;
    34.             bool wasEnabled = GUI.enabled;
    35.             GUI.enabled = false;
    36.             EditorGUI.PropertyField(position, property, label, true);
    37.             GUI.enabled = wasEnabled;
    38.         }
    39.         else
    40.             EditorGUI.HelpBox(position, $"Missing {fieldInfo.FieldType}", MessageType.Warning);
    41.     }
    42. }
     
  2. PaperPrototype

    PaperPrototype

    Joined:
    Jan 3, 2018
    Posts:
    6
    I wish Unity would add this to their engine by default. This is really great.
     
  3. PaperPrototype

    PaperPrototype

    Joined:
    Jan 3, 2018
    Posts:
    6
    I got the code working here is the updated code:

    /Assets/Code/ComponentReferenceAttribute.cs
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ComponentReferenceAttribute : PropertyAttribute
    4. {
    5.     // ReSharper disable twice UnusedParameter.Local
    6.     public ComponentReferenceAttribute(bool findInChildren = false, bool findInParent = false)
    7.     { }
    8. }
    /Assets/Code/Editor/ComponentReferenceDrawer.cs
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Reflection;
    3. using UnityEngine;
    4. using UnityEditor;
    5.  
    6.  
    7. [CustomPropertyDrawer(typeof(ComponentReferenceAttribute))]
    8. public class ComponentReferenceDrawer : PropertyDrawer
    9. {
    10.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    11.     {
    12.         IList<CustomAttributeData> customAttributesData = fieldInfo.GetCustomAttributesData();
    13.  
    14.         CustomAttributeData customAttributeData = null;
    15.  
    16.         for(int i = 0; i < customAttributesData.Count; i++)
    17.         {
    18.             if (customAttributesData[i].AttributeType == typeof(ComponentReferenceAttribute))
    19.             {
    20.                 customAttributeData = customAttributesData[i];
    21.                 break;
    22.             }
    23.         }
    24.  
    25.         // .FirstOrDefault(a => a.AttributeType == typeof(ComponentReferenceAttribute));
    26.         if (customAttributeData == null)
    27.         {
    28.             EditorGUI.HelpBox(position, "Missing attribute data", MessageType.Warning);
    29.             return;
    30.         }
    31.  
    32.         var targetObject = property.serializedObject.targetObject as Component;
    33.         if (targetObject == null)
    34.         {
    35.             EditorGUI.HelpBox(position, "Invalid serialized object", MessageType.Warning);
    36.             return;
    37.         }
    38.  
    39.         var searchInChildren = (bool)customAttributeData.ConstructorArguments[0].Value;
    40.         var searchInParent = (bool)customAttributeData.ConstructorArguments[1].Value;
    41.  
    42.         var component = property.objectReferenceValue as Component;
    43.         if (component == null) component = targetObject.GetComponent(fieldInfo.FieldType);
    44.         if (component == null && searchInChildren)
    45.             component = targetObject.GetComponentInChildren(fieldInfo.FieldType);
    46.         if (component == null && searchInParent)
    47.             component = targetObject.GetComponentInParent(fieldInfo.FieldType);
    48.  
    49.         if (component != null)
    50.         {
    51.             property.objectReferenceValue = component;
    52.             bool wasEnabled = GUI.enabled;
    53.             GUI.enabled = false;
    54.             EditorGUI.PropertyField(position, property, label, true);
    55.             GUI.enabled = wasEnabled;
    56.         }
    57.         else
    58.             EditorGUI.HelpBox(position, $"Missing {fieldInfo.FieldType}", MessageType.Warning);
    59.     }
    60. }

    Here is how would use the code:
    /Assets/Code/Chunk.cs
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [RequireComponent(typeof(MeshRenderer))]
    4. [RequireComponent(typeof(MeshFilter))]
    5. public class Chunk : MonoBehaviour
    6. {
    7.     [ComponentReference] public MeshRenderer meshRenderer;
    8.     [ComponentReference] public MeshFilter meshFilter;
    9.  
    10.     void Start()
    11.     {
    12.      
    13.     }
    14. }
    15.  
     
  4. olejuer

    olejuer

    Joined:
    Dec 1, 2014
    Posts:
    210
    It seems the code wasn't working for you, because you were missing the
    using System.Linq; 
    statement. Nothing wrong with your solution, though. But Linq is a really useful library that's totally worth checking out :)