Skip to content

Instantly share code, notes, and snippets.

@ababilinski
Created May 27, 2025 19:26
Show Gist options
  • Select an option

  • Save ababilinski/1b9b9ad47fedaa2c2ecd7c5a13d45f57 to your computer and use it in GitHub Desktop.

Select an option

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.
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