Skip to content

Instantly share code, notes, and snippets.

@perky
Created April 16, 2019 12:31
Show Gist options
  • Save perky/dacab19060cdcb7fc00ee2fa6cd9ab4b to your computer and use it in GitHub Desktop.
Save perky/dacab19060cdcb7fc00ee2fa6cd9ab4b to your computer and use it in GitHub Desktop.
A little script for Unity that helps binding references to other components in the scene.
using UnityEngine;
using System.Linq;
using System;
using System.Reflection;
public class BindComponent : PropertyAttribute
{
public enum FindMode
{
InSelf, // Find the component on the calling GameObject.
InChildren, // Find the component in the children of the calling GameObjet.
InParent, // Find the component up the parent hierarchy of the calling GameObject.
InScene, // Find the 1st instance of the component anywhere in the active scene.
ViaPath // Find the component via a hierarchy path relative to the calling GameObject.
}
public string path;
public FindMode findMode = FindMode.InSelf;
/// <summary>
/// Bind a reference to a component that exists on the same game object.
/// </summary>
public BindComponent()
{
}
/// <summary>
/// Bind a reference to a component that exists on a child game object.
/// Must specify exact relative path.
/// </summary>
/// <param name="childPath"></param>
public BindComponent(string childPath)
{
this.findMode = FindMode.ViaPath;
this.path = childPath;
}
/// <summary>
/// Bind a reference to a component that exists in the scene.
/// A FindMode must be specified to indicate how to find that component.
/// </summary>
/// <param name="findMode"></param>
public BindComponent(FindMode findMode)
{
if (findMode == FindMode.ViaPath)
{
Debug.LogError("BindComponent: Do not use FindMode.ViaPath. Use [BindComponent(\"relative/path\")] instead.");
}
this.findMode = findMode;
}
/// <summary>
/// Performs the actual binding. Typically you want to call this in Awake().
/// For example: Awake() { BindComponent.Bind(this); }
/// </summary>
/// <param name="component">The component that is collecting the references (typically you pass 'this')</param>
/// <typeparam name="T">The type of the component passed in the component argument. (This is automatically inferred.)</typeparam>
public static void Bind<T>(T component)
{
var attributeType = typeof(BindComponent);
var fields = typeof(T)
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(x => x.IsDefined(attributeType, false))
.ToArray();
foreach (var field in fields)
{
var attr = field.GetCustomAttribute(attributeType, false) as BindComponent;
var behaviour = component as MonoBehaviour;
if (attr.path == null)
{
SetValue(field, component, behaviour, attr.findMode);
}
else if (attr.findMode == FindMode.ViaPath)
{
string[] children = attr.path.Split('/');
Transform transform = behaviour.transform;
int childIndex = 0;
while (childIndex < children.Length)
{
string childName = children[childIndex++];
transform = transform.Find(childName);
if (transform == null)
{
Debug.LogError("BindComponent: Could not find child named " + childName + " in path " + attr.path, behaviour);
}
}
SetValue(field, component, transform, attr.findMode);
}
}
}
static void SetValue<T>(FieldInfo field, T source, Component destination, FindMode findMode)
{
Component componentToBind = null;
switch (findMode)
{
default:
case FindMode.InSelf:
componentToBind = destination.GetComponent(field.FieldType);
break;
case FindMode.InChildren:
componentToBind = destination.GetComponentInChildren(field.FieldType);
break;
case FindMode.InParent:
componentToBind = destination.GetComponentInParent(field.FieldType);
break;
case FindMode.InScene:
componentToBind = (Component)UnityEngine.Object.FindObjectOfType(field.FieldType);
break;
}
if (componentToBind == null)
{
Debug.LogError("BindComponent: Could not find component " + field.FieldType.Name + " for field " + field.Name, source as Component);
}
field.SetValue(source, componentToBind);
}
}
using UnityEngine;
using UnityEngine.UI;
public class BindComponentExample : MonoBehaviour
{
// Find a Button component in the child named "PlayButton".
[BindComponent("PlayButton")] Button playButton;
// Find a Button component in the child named "RestartButton".
[BindComponent("RestartButton")] Button restartButton;
// Find an AudioSource component on the same GameObject.
[BindComponent()] AudioSource uiAudio;
// Find the GameState component in the scene.
[BindComponent(BindComponent.FindMode.InScene)] GameState gameState;
void Awake()
{
// Do the actual binding. This is exposed in case you have your own custom lifecycle
// and wish to perform the bind elsewhere.
BindComponent.Bind(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment