Personally I always try to avoid writing editor scripts as it feels like too much work for small changes, but I also love writing scripts that run in the editor to improve work flow and such… I’ve recently stumbled upon a quick an easy alternative to set up an in-edtor button that can run a script, without needing to write a separate script to do so!
[ExecuteInEditMode]
public class myClass : Monobehaviour
{
public bool buttonDisplayName; //“run” or “generate” for example
Update ()
{
if (buttonDisplayName)
ButtonFunction ();
buttonDisplayName = false;
}
}
The concept is actually pretty simple, but I’ll explain it a bit. When a script extends monodevelop and has the tag ExecuteInEditMode, monodevelop behaves differently, such that Update is called not every frame, but whenever something in the scene changes. This means when the boolean buttonDisplayName is ticked on, update is called. Update checks if the boolean is true, runs the function, and then sets the bool back to false, before the renderer even displays it as being turned on.
I’ve been using this to test out a prefab generator which works perfectly whenever I hit my tiny “Run” button. The only issue is that when I tried measuring how long it took to operate, Time.time no longer works. But that was easily fixed with System.DateTime.Now and similar commands.
Thoughts? Has this already been done before and I just missed the memo? Figured I’d share it, as I found it pretty useful and easy to set up.
Yes, but I only stumbled randomly over it as well. In the slack chat someone shared a really cool way to do this where you just set an attribute [EditorButton] over a method and you get a proper button in the inspector that you can click to call the method. Once you have the asset installed that’s all you need to do (and exactly how I wanted this to be handled).
One of the Unite Melbourne speakers used reflection and attributes in a similar fashion to drive the entire in game UI. Worked well for UI heavy simulation type games.
I tend to do a lot of editor scripting. I include an inspector in my default template, and have it right on the script so I can easily add editor stuff directly. So to just throw a button or two on script for testing, I might do something like this:
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
public class TestScript : MonoBehaviour
{
[SerializeField] public bool cool_done = false;
[SerializeField] public bool silly_done = false;
[SerializeField] public Color random_color = Color.red;
void Start(){}
public void DoSomethingCool()
{
Debug.Log("Something Cool!");
cool_done = !cool_done;
}
public void DoSomethingSilly()
{
Debug.Log("Something Silly!");
random_color = new Color(Random.value, Random.value, Random.value, 1f);
silly_done = !silly_done;
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(TestScript))]
public class TestScriptEditor : Editor {
public TestScript script;
public void OnEnable() { script = (TestScript)target; }
public override void OnInspectorGUI()
{
if(GUILayout.Button("Do Something Cool!"))
{
script.DoSomethingCool();
}
base.OnInspectorGUI();
GUI.backgroundColor = script.random_color;
if(GUILayout.Button("Do Something Silly!"))
{
script.DoSomethingSilly();
}
GUI.backgroundColor = Color.white;
}
}
#endif
My default script template looks (pretty much) like this:
// -----------------------------------------------------------------------
// <copyright file="TestScript.cs">
// Copyright (c) 2016 My Name
// </copyright>
// <contact>My Name (myemail@address.com)</contact>
// <description>Description</description>
// -----------------------------------------------------------------------
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
//using UnityEngine.UI;
//using UnityEngine.EventSystems;
//using DG.Tweening;
namespace MyNamespace
{
public class TestScript : MonoBehaviour
{
[SerializeField] public int version;
}
}
// extra class
namespace MyNamespace
{
[System.Serializable]
public class BonusClass
{
[SerializeField] public int version;
}
}
// editor
#if UNITY_EDITOR
namespace MyNamespace
{
[CustomEditor(typeof(TestScript))]
public class TestScriptEditor : Editor {
private SerializedObject m_Obj;
private float indent_base;
public TestScript script;
public void OnEnable()
{
script = (TestScript)target;
m_Obj = new SerializedObject(target);
}
public override void OnInspectorGUI()
{
indent_base = EditorGUIUtility.labelWidth;
GUI.changed = false;
m_Obj.Update();
GUI.backgroundColor = GUI.color = Color.green;
EditorGUILayout.BeginVertical(gs_main_block()); {
GUI.backgroundColor = GUI.color = Color.white;
if(GUILayout.Button("BUTTON",gs_button()))
{
// action
}
} EditorGUILayout.EndVertical();
m_Obj.ApplyModifiedProperties();
base.OnInspectorGUI();
}
GUIStyle gs_button()
{
GUIStyle g = new GUIStyle("LargeButton");
return g;
}
GUIStyle gs_main_block()
{
GUIStyle g = new GUIStyle("sv_label_0");
g.padding = new RectOffset(5,5,5,8);
g.margin = new RectOffset(5,5,5,5);
g.border = new RectOffset(8,8,6,6);
g.fixedHeight =0;
return g;
}
}
}
#endif