Created
May 27, 2025 19:26
-
-
Save ababilinski/1b9b9ad47fedaa2c2ecd7c5a13d45f57 to your computer and use it in GitHub Desktop.
Tiny extension helpers that turn the ubiquitous GetComponent<T>() pattern into a zero-cost cached call. Supports children, parents, scene-wide singletons, and a coroutine to wait for a type to appear.
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 UnityEngine; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using UnityEngine.Events; | |
| using Object = UnityEngine.Object; | |
| // ReSharper disable UnusedParameter.Global | |
| namespace Common.Extensions | |
| { | |
| /// <summary> | |
| /// Drop-in replacements for GetComponent and FindComponent that cache the result in a <c>ref</c> field. | |
| /// <remarks> | |
| /// <para> | |
| /// Extension methods for Unity components that provide caching functionality | |
| /// to improve performance when frequently accessing components. | |
| /// Requires Unity 6.0 or newer. | |
| /// </para> | |
| /// </remarks> | |
| /// Usage example: | |
| /// <example> | |
| /// <code> | |
| /// private Rigidbody rb; | |
| /// void FixedUpdate () | |
| /// { | |
| /// rb = this.GetCached(ref rb); | |
| /// rb.AddForce(Vector3.up); | |
| /// } | |
| /// </code> | |
| /// </example> | |
| /// </summary> | |
| public static class ComponentExtensions | |
| { | |
| #region FindObjectByType with Caching | |
| /// <summary> | |
| /// Finds the first object of type T with caching to avoid repeated searches. | |
| /// Uses Unity 6+ FindFirstObjectByType for deterministic results. | |
| /// </summary> | |
| public static T FindCachedFirstObjectByType<T>(this Object target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = Object.FindFirstObjectByType<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| /// <summary> | |
| /// Finds any object of type T with caching (faster but non-deterministic). | |
| /// Uses Unity 6+ FindAnyObjectByType for better performance. | |
| /// </summary> | |
| public static T FindCachedAnyObjectByType<T>(this Object target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = Object.FindAnyObjectByType<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| // Overloads for different target types | |
| public static T FindCachedFirstObjectByType<T>(this Component target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return ((Object)target).FindCachedFirstObjectByType(ref cachedComponent, includeInactive); | |
| } | |
| public static T FindCachedAnyObjectByType<T>(this Component target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return ((Object)target).FindCachedAnyObjectByType(ref cachedComponent, includeInactive); | |
| } | |
| public static T FindCachedFirstObjectByType<T>(this Transform target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return ((Object)target).FindCachedFirstObjectByType(ref cachedComponent, includeInactive); | |
| } | |
| public static T FindCachedAnyObjectByType<T>(this Transform target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return ((Object)target).FindCachedAnyObjectByType(ref cachedComponent, includeInactive); | |
| } | |
| public static T FindCachedFirstObjectByType<T>(this GameObject target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return ((Object)target).FindCachedFirstObjectByType(ref cachedComponent, includeInactive); | |
| } | |
| public static T FindCachedAnyObjectByType<T>(this GameObject target, ref T cachedComponent, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return ((Object)target).FindCachedAnyObjectByType(ref cachedComponent, includeInactive); | |
| } | |
| #endregion | |
| #region Component Caching Extensions | |
| /// <summary> | |
| /// Gets a component with caching for improved performance on repeated calls. | |
| /// Returns null if target is null or component doesn't exist. | |
| /// </summary> | |
| public static T GetCachedComponent<T>(this Component target, ref T cachedComponent) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponent<T>(); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedComponent<T>(this Transform target, ref T cachedComponent) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponent<T>(); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedComponent<T>(this GameObject target, ref T cachedComponent) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponent<T>(); | |
| } | |
| return cachedComponent; | |
| } | |
| #endregion | |
| #region ComponentInChildren Caching Extensions | |
| public static T GetCachedComponentInChildren<T>(this Component target, ref T cachedComponent, bool includeInactive = false) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponentInChildren<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedComponentInChildren<T>(this Transform target, ref T cachedComponent, bool includeInactive = false) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponentInChildren<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedComponentInChildren<T>(this GameObject target, ref T cachedComponent, bool includeInactive = false) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponentInChildren<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| #endregion | |
| #region ComponentInParent Caching Extensions | |
| public static T GetCachedComponentInParent<T>(this Component target, ref T cachedComponent) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponentInParent<T>(); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedComponentInParent<T>(this Transform target, ref T cachedComponent) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponentInParent<T>(); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedComponentInParent<T>(this GameObject target, ref T cachedComponent) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetComponentInParent<T>(); | |
| } | |
| return cachedComponent; | |
| } | |
| #endregion | |
| #region Flexible Component Finding | |
| /// <summary> | |
| /// Returns the component from itself, children, or parent (prioritizes self, then children, then parent). | |
| /// </summary> | |
| public static T GetAnyComponent<T>(this Component target, bool includeInactive = true) | |
| { | |
| if (target == null) return default(T); | |
| // First try to get from self | |
| T component = target.GetComponent<T>(); | |
| if (component != null) return component; | |
| // Then try children | |
| T child = target.GetComponentInChildren<T>(includeInactive); | |
| if (child != null) return child; | |
| // Finally try parent | |
| return target.GetComponentInParent<T>(); | |
| } | |
| public static T GetAnyComponent<T>(this Transform target, bool includeInactive = true) | |
| { | |
| if (target == null) return default(T); | |
| T component = target.GetComponent<T>(); | |
| if (component != null) return component; | |
| T child = target.GetComponentInChildren<T>(includeInactive); | |
| if (child != null) return child; | |
| return target.GetComponentInParent<T>(); | |
| } | |
| public static T GetAnyComponent<T>(this GameObject target, bool includeInactive = true) | |
| { | |
| if (target == null) return default(T); | |
| T component = target.GetComponent<T>(); | |
| if (component != null) return component; | |
| T child = target.GetComponentInChildren<T>(includeInactive); | |
| if (child != null) return child; | |
| return target.GetComponentInParent<T>(); | |
| } | |
| /// <summary> | |
| /// Cached version of GetAnyComponent for performance. | |
| /// </summary> | |
| public static T GetCachedAnyComponent<T>(this Component target, ref T cachedComponent, bool includeInactive = true) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetAnyComponent<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedAnyComponent<T>(this Transform target, ref T cachedComponent, bool includeInactive = true) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetAnyComponent<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| public static T GetCachedAnyComponent<T>(this GameObject target, ref T cachedComponent, bool includeInactive = true) | |
| { | |
| if (target == null) return default(T); | |
| if (cachedComponent == null) | |
| { | |
| cachedComponent = target.GetAnyComponent<T>(includeInactive); | |
| } | |
| return cachedComponent; | |
| } | |
| #endregion | |
| #region Async Component Finding | |
| /// <summary> | |
| /// Asynchronously finds the first object of type T and invokes a callback when found. | |
| /// Useful for objects that might not exist immediately. | |
| /// </summary> | |
| public static Coroutine YieldFindFirstObjectByType<T>(this MonoBehaviour target, UnityAction<T> onFound, float timeout = 0f, FindObjectsInactive includeInactive = FindObjectsInactive.Exclude) where T : Object | |
| { | |
| return target.StartCoroutine(StartYieldFindFirstObjectByType(onFound, timeout, includeInactive)); | |
| } | |
| private static IEnumerator StartYieldFindFirstObjectByType<T>(UnityAction<T> onFound, float timeout, FindObjectsInactive includeInactive) where T : Object | |
| { | |
| float startTime = Time.time; | |
| while (true) | |
| { | |
| T foundObject = Object.FindFirstObjectByType<T>(includeInactive); | |
| if (foundObject != null) | |
| { | |
| onFound?.Invoke(foundObject); | |
| yield break; | |
| } | |
| if (timeout > 0f && Time.time - startTime >= timeout) | |
| { | |
| Debug.LogWarning($"YieldFindFirstObjectByType<{typeof(T).Name}> timed out after {timeout} seconds"); | |
| onFound?.Invoke(null); | |
| yield break; | |
| } | |
| yield return null; | |
| } | |
| } | |
| private static IEnumerator StartYieldFindAnyObjectByType<T>(UnityAction<T> onFound, float timeout, FindObjectsInactive includeInactive) where T : Object | |
| { | |
| float startTime = Time.time; | |
| while (true) | |
| { | |
| T foundObject = Object.FindAnyObjectByType<T>(includeInactive); | |
| if (foundObject != null) | |
| { | |
| onFound?.Invoke(foundObject); | |
| yield break; | |
| } | |
| if (timeout > 0f && Time.time - startTime >= timeout) | |
| { | |
| Debug.LogWarning($"YieldFindAnyObjectByType<{typeof(T).Name}> timed out after {timeout} seconds"); | |
| onFound?.Invoke(null); | |
| yield break; | |
| } | |
| yield return null; | |
| } | |
| } | |
| #endregion | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment