Last active
June 20, 2023 13:06
-
-
Save capnslipp/ac26e38fce770b5c4594 to your computer and use it in GitHub Desktop.
Unity3D MonoBehaviourPopulateExtension & friends
This file contains 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
/// @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+" "[email protected]()+".", @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; | |
} | |
} |
This file contains 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
/// @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; | |
} | |
} |
This file contains 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; | |
public class ExampleSomeOtherMB : MonoBehaviour | |
{ | |
public int foo = 26; | |
} |
This file contains 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
/// @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