In a rush? Grab the code off of GitHub
Have you ever wanted to call a method on one of your MonoBehaviours right from the editor? There are plenty ways to do this.
- You could make your own custom editor and add buttons in an OnInspectorGUI. This is recommended but annoying for one-offs.
- You could use my handy 5 minute custom editor.
- There is probably an asset store asset that is more robust than this. But rolling-your-own is more fun. Let's give it a shot.
so how will we approach this?
The approach is 3 fold:
- Create an
Attribute
to decorate a method that you wish to expose as a button - Make a custom editor that will look for those on all
MonoBehaviour
's and descendants and draw/handle the buttons. - Decorate any method you wish to expose with the attribute
At the top of this article you will find an image of the look we are going for.
And the code that created that method:
using UnityEngine;
using CatchCo; // My namespace to avoid conflicts
public class TestMonoBehaviour : MonoBehaviour
{
// This is our fancy attribute. Easy no?
[ExposeMethodInEditor]
public void DoThePublicThing()
{
Debug.Log("DoThePublicThing");
}
// We'll make it work on private methods too
[ExposeMethodInEditor]
private void DoThePrivateThing()
{
Debug.Log("Thing done in private");
}
}
lets look at the attribute first
The attribute is not very interesting in this case. It's declared as an attribute that can only decorate methods. The only purpose it currently serves is to help identify which methods need exposing.
using System;
// Place this file in any folder that is or is a descendant of a folder named "Scripts"
namespace CatchCo
{
// Restrict to methods only
[AttributeUsage(AttributeTargets.Method)]
public class ExposeMethodInEditorAttribute : Attribute
{
}
}
now for the "hard" part
Well that brings us to the part that actually does anything. It's a CustomEditor
that works on any MonoBehaviour or decendent. Code first and then we'll talk about the approach and limitations. I've numbered important lines, see the explanation below.
using UnityEngine;
using UnityEditor;
using System.Reflection;
// Place this file in any folder that is or is a descendant of a folder named "Editor"
namespace CatchCo
{
[CanEditMultipleObjects] // Don't ruin everyone's day
[CustomEditor(typeof(MonoBehaviour), true)] // Target all MonoBehaviours and descendants
public class MonoBehaviourCustomEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector(); // Draw the normal inspector
// Currently this will only work in the Play mode. You'll see why
if (Application.isPlaying)
{
// Get the type descriptor for the MonoBehaviour we are drawing
var type = target.GetType();
// Iterate over each private or public instance method (no static methods atm)
foreach (var method in type.GetMethods(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance))
{
// make sure it is decorated by our custom attribute
var attributes = method.GetCustomAttributes(typeof(ExposeMethodInEditorAttribute), true);
if (attributes.Length > 0)
{
if (GUILayout.Button("Run: " + method.Name))
{
// If the user clicks the button, invoke the method immediately.
// There are many ways to do this but I chose to use Invoke which only works in Play Mode.
((MonoBehaviour)target).Invoke(method.Name, 0f);
}
}
}
}
}
}
}
points of interest
- Currently this only handles parameterless methods. It wouldn't be difficult to draw fields for the parameters but I didn't require that at the time. Feel free to add it in.
- Of course it only works in Play mode right now. Again, just a choice based on my needs. Though if you decide to execute anything outside of Play mode do be careful.
- This has the limitation of not working on any MonoBehaviour with a current
CustomEditor
. It's not too much of a problem since you would already have aCustomEditor
to work with.
So that's it for this quick share. Let me know via twitter (@CatchCo) if you decide to use and/or modify it.