Skip to content

Instantly share code, notes, and snippets.

@capnslipp
Last active June 20, 2023 13:06
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save capnslipp/ac26e38fce770b5c4594 to your computer and use it in GitHub Desktop.
Save capnslipp/ac26e38fce770b5c4594 to your computer and use it in GitHub Desktop.
Unity3D MonoBehaviourPopulateExtension & friends
/// @creator: Slipp Douglas Thompson
/// @license: Public Domain per The Unlicense. See <http://unlicense.org/>.
/// @purpose: More-flexible GO-tree searching for Components by type, configurable via `SearchExtent` enum arg.
/// @why: Because this functionality should be built-into Unity.
/// @usage: Call `this.gameObject.FindComponent<«component-type»>(«SearchExtent-type»);`.
/// @intended project path: Assets/Plugins/UnityEngine Extensions/GameObjectFindComponentExtension.cs
/// @interwebsouce: https://gist.github.com/capnslipp/ac26e38fce770b5c4594
using System;
using UnityEngine;
public static class GameObjectFindComponentExtension
{
public enum SearchExtent {
Local = 1,
Ancestry, // you are part of your own ancestry
Ancestors, // you are *not* your own ancestor
Descendantry, // you are part of your own descendantry
Descendants, // you are *not* your own descendant
None = 0
}
public static Component FindComponent(this GameObject @this,
Type componentType,
SearchExtent extent=SearchExtent.Local
)
{
if (@this == null)
throw new ArgumentNullException("This extension method was called on a null object, which is not allowed.", "@this");
string failMessageHead = null;
Component foundComponent = SearchForComponent(@this, componentType, extent, out failMessageHead);
if (foundComponent == null)
Debug.LogWarning(failMessageHead+" "+@this.GetType()+".", @this);
return foundComponent;
}
public static ComponentT FindComponent<ComponentT>(this GameObject @this,
SearchExtent extent=SearchExtent.Local
) where ComponentT : Component
{
return (ComponentT)FindComponent(@this, typeof(ComponentT), extent);
}
static Component SearchForComponent(
GameObject localGameObject,
Type componentType,
SearchExtent extent,
out string failMessageHead
)
{
switch (extent) {
case SearchExtent.Local:
return SearchForComponentInLocal(localGameObject, componentType, out failMessageHead);
case SearchExtent.Ancestry:
return SearchForComponentInAncestry(localGameObject, componentType, out failMessageHead, skipLocal: false);
case SearchExtent.Ancestors:
return SearchForComponentInAncestry(localGameObject, componentType, out failMessageHead, skipLocal: true);
case SearchExtent.Descendantry:
return SearchForComponentInDescendantry(localGameObject, componentType, out failMessageHead, skipLocal: false);
case SearchExtent.Descendants:
return SearchForComponentInDescendantry(localGameObject, componentType, out failMessageHead, skipLocal: true);
default:
failMessageHead = null;
return null;
}
}
static Component SearchForComponentInLocal(
GameObject localGameObject,
Type componentType,
out string failMessageHead
)
{
Component foundComponent = localGameObject.GetComponent(componentType);
if (foundComponent != null) {
failMessageHead = null;
return foundComponent;
}
else {
failMessageHead = string.Format("No "+componentType+" assigned and unable to find one for this");
return null;
}
}
static Component SearchForComponentInAncestry(
GameObject localGameObject,
Type componentType,
out string failMessageHead,
bool skipLocal=false
)
{
Transform searchTransform = localGameObject.transform;
if (skipLocal)
searchTransform = localGameObject.transform.parent;
Component foundComponent = null;
while (searchTransform != null) {
foundComponent = searchTransform.GetComponent(componentType);
if (foundComponent != null)
break;
searchTransform = searchTransform.parent;
}
if (foundComponent != null) {
failMessageHead = null;
return foundComponent;
}
else {
failMessageHead = string.Format("Unable to find a "+componentType+" "+(skipLocal ? "" : "on or ")+"above this");
return null;
}
}
static Component SearchForComponentInDescendantry(
GameObject localGameObject,
Type componentType,
out string failMessageHead,
bool skipLocal=false
)
{
failMessageHead = null;
Component[] foundComponents = localGameObject.GetComponentsInChildren(componentType, includeInactive: true); // actually searches self and all descendents, despite the name
foreach (Component oneComponent in foundComponents)
{
if (oneComponent.gameObject == localGameObject) {
if (skipLocal)
continue;
else
return oneComponent;
}
else { // non-local
return oneComponent;
}
}
failMessageHead = string.Format("Unable to find a "+componentType+" "+(skipLocal ? "" : "on or ")+"under this");
return null;
}
}
/// @creator: Slipp Douglas Thompson
/// @license: Public Domain per The Unlicense. See <http://unlicense.org/>.
/// @purpose: Populates fields at runtime with references to other components with a one-liner. Use to provide more GO-tree searching capability and better performance (via caching) than GetComponent.
/// @why: Because this functionality should be built-into Unity.
/// @usage: Call `this.PopulateComponentField(ref this.«component-field», «SearchExtent-type»);` inside Awake() for each field to be populated.
/// Optionally, create an additional field of type `GameObjectFindComponentExtension.SearchExtent` and pass that into the PopulateComponentField<>() call in order to allow configuring the SerachExtent in the editor.
/// @intended project path: Assets/Plugins/UnityEngine Extensions/MonoBehaviourPopulateExtension.cs
/// @interwebsouce: https://gist.github.com/capnslipp/ac26e38fce770b5c4594
using System;
using UnityEngine;
using SearchExtent=GameObjectFindComponentExtension.SearchExtent;
public static class MonoBehaviourPopulateExtension
{
public static void PopulateComponentField(this MonoBehaviour @this,
Type componentType,
ref Component field,
SearchExtent extent=SearchExtent.Local,
bool forceRepopulate=false
)
{
if (@this == null)
throw new ArgumentNullException("This extension method was called on a null object, which is not allowed.", "@this");
if (field != null && !forceRepopulate)
return;
Component foundComponent = @this.gameObject.FindComponent(componentType, extent);
if (foundComponent != null)
field = foundComponent;
}
/// It is probably best to call from within Awake (as opposed to OnEnable or Start) so that its values are ready by the time OnEnable and Start happen.
/// Although this means that another script creating this component (via AddComponent) doesn't have a chance to assign values it may know before we wastefully search out values, Unity doesn't provide us many other options—
/// there is no AddComponentDisabled() equivalent, and
/// Awake is our only chance to set these up before OnEnable (which is often used for repeatable setup logic requiring references to be ready).
/// Awake also ensures that this heavy lifting is done synchronously before we render the first frame of a new scene, hopefully avoiding most in-action stutters.
public static void PopulateComponentField<ComponentT>(this MonoBehaviour @this,
ref ComponentT field,
SearchExtent extent=SearchExtent.Local,
bool forceRepopulate=false
) where ComponentT : Component
{
Component fieldStandin = field;
PopulateComponentField(@this, typeof(ComponentT), ref fieldStandin, extent, forceRepopulate);
field = (ComponentT)fieldStandin;
}
}
using UnityEngine;
public class ExampleSomeOtherMB : MonoBehaviour
{
public int foo = 26;
}
/// @requires `HideInNormalInspector.cs`, from https://gist.github.com/capnslipp/8138106
using UnityEngine;
using SearchExtent=GameObjectFindComponentExtension.SearchExtent;
public class ExampleUsage : MonoBehaviour
{
public BoxCollider boxCollider;
[HideInNormalInspector] public SearchExtent boxCollider_SearchExtent = SearchExtent.Local;
public ExampleSomeOtherMB someOtherMB;
[HideInNormalInspector] public SearchExtent someOtherMB_SearchExtent = SearchExtent.Descendantry;
void Awake()
{
this.PopulateComponentField(ref this.boxCollider, this.boxCollider_SearchExtent);
this.PopulateComponentField(ref this.someOtherMB, this.someOtherMB_SearchExtent);
}
void Update()
{
// @fillin: Do werk here using `this.boxCollider` and `this.someOtherMB`.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment