- Home /
How to have a gradient editor in an editor script?
I was making an editor script where you have to define a gradient, and I noticed there's a gradient editor in the particle system editor: http://docs.unity3d.com/Documentation/Manual/GradientEditor.html and there's also the gradient class you can access from script: http://docs.unity3d.com/Documentation/ScriptReference/Gradient.html
So is there a way to have this in an editor script? I mean something like EditorGUILayout.GradientField (like there is EditorGUILayout.ColorField)
You would need to use reflection since UnityEditor.EditorGUILayout.GradientField
is declared for internal use only and its return is UnityEditor.Gradient
which is also declared for internal use only. You should make a feature request on the Unity Feedback site because they often get listened to!
hm, alright, thanks. I'm still using 3.5something though, so if they do change this, I would have to switch to 4 to be able to use it. :s
Answer by numberkruncher · Apr 21, 2013 at 07:27 PM
In Unity 3.5.7 this functionality is provided entirely by the editor API (whilst in Unity 4.x the Gradient
data class is available from the runtime library).
Here is a quick reflection based hack which I have quickly put together for you which allows you to make use of the gradient editor field in both Unity 3.5.7 and Unity 4.x. To make this easier to use I have created a wrapper, but this functionality is strictly editor-only.
I would recommend adding error handling to the reflection side of things, this was literally just a quick mashup to demonstrate the concept which I mentioned in my earlier comment. I hope that this will be of some help to you :)
Side Note: It probably be relatively easy to replicate this with an entirely custom editor field since you could probably just use vertex colors to simulate the gradient blending.
Assets/Editor/GradientTest/GUIGradientField.cs
using UnityEngine;
using UnityEditor;
using System.Reflection;
using Type = System.Type;
public static class GUIGradientField {
#region Initial Setup
private static MethodInfo s_miGradientField1;
private static MethodInfo s_miGradientField2;
static GUIGradientField() {
// Get our grubby hands on hidden "GradientField" :)
Type tyEditorGUILayout = typeof(EditorGUILayout);
s_miGradientField1 = tyEditorGUILayout.GetMethod("GradientField", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string), GradientWrapper.s_tyGradient, typeof(GUILayoutOption[]) }, null);
s_miGradientField2 = tyEditorGUILayout.GetMethod("GradientField", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { GradientWrapper.s_tyGradient, typeof(GUILayoutOption[]) }, null);
}
#endregion
public static GradientWrapper GradientField(string label, GradientWrapper gradient, params GUILayoutOption[] options) {
if (gradient == null)
gradient = new GradientWrapper();
gradient.GradientData = s_miGradientField1.Invoke(null, new object[] { label, gradient.GradientData, options });
return gradient;
}
public static GradientWrapper GradientField(GradientWrapper gradient, params GUILayoutOption[] options) {
if (gradient == null)
gradient = new GradientWrapper();
gradient.GradientData = s_miGradientField1.Invoke(null, new object[] { gradient.GradientData, options });
return gradient;
}
}
Assets/Editor/GradientTest/GradientWrapper.cs
using UnityEngine;
using UnityEditor;
using System.Reflection;
using System.Linq;
using Activator = System.Activator;
using Array = System.Array;
using Type = System.Type;
[System.Serializable]
public class GradientWrapper {
/// <summary>
/// Wrapper for <c>GradientColorKey</c>.
/// </summary>
public struct ColorKey {
public Color color;
public float time;
public ColorKey(Color color, float time) {
this.color = color;
this.time = time;
}
}
/// <summary>
/// Wrapper for <c>GradientAlphaKey</c>.
/// </summary>
public struct AlphaKey {
public float alpha;
public float time;
public AlphaKey(float alpha, float time) {
this.alpha = alpha;
this.time = time;
}
}
#region Initial Setup
/// <summary>
/// Type of gradient.
/// </summary>
public static Type s_tyGradient;
#if (UNITY_3_5 || UNITY_3_6 || UNITY_3_7 || UNITY_3_8 || UNITY_3_9)
private static MethodInfo s_miEvaluate;
private static PropertyInfo s_piColorKeys;
private static PropertyInfo s_piAlphaKeys;
private static Type s_tyGradientColorKey;
private static Type s_tyGradientAlphaKey;
#endif
/// <summary>
/// Perform one-off setup when class is accessed for first time.
/// </summary>
static GradientWrapper() {
#if (UNITY_3_5 || UNITY_3_6 || UNITY_3_7 || UNITY_3_8 || UNITY_3_9)
Assembly editorAssembly = typeof(Editor).Assembly;
s_tyGradientColorKey = editorAssembly.GetType("UnityEditor.GradientColorKey");
s_tyGradientAlphaKey = editorAssembly.GetType("UnityEditor.GradientAlphaKey");
// Note that `Gradient` is defined in the editor namespace in Unity 3.5.7!
s_tyGradient = editorAssembly.GetType("UnityEditor.Gradient");
s_miEvaluate = s_tyGradient.GetMethod("CalcColor", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(float) }, null);
s_piColorKeys = s_tyGradient.GetProperty("colorKeys", BindingFlags.Public | BindingFlags.Instance);
s_piAlphaKeys = s_tyGradient.GetProperty("alphaKeys", BindingFlags.Public | BindingFlags.Instance);
#else
// In Unity 4 this is easy :)
s_tyGradient = typeof(Gradient);
#endif
}
#endregion
#if (UNITY_3_5 || UNITY_3_6 || UNITY_3_7 || UNITY_3_8 || UNITY_3_9)
#region Unity 3.5.7 Implementation
private object _gradient = Activator.CreateInstance(s_tyGradient);
public object GradientData {
get { return _gradient; }
set { _gradient = value; }
}
public Color Evaluate(float time) {
return (Color)s_miEvaluate.Invoke(_gradient, new object[] { time });
}
public void SetKeys(ColorKey[] colorKeys, AlphaKey[] alphaKeys) {
if (colorKeys != null) {
Array colorKeyParam = (Array)Activator.CreateInstance(s_tyGradientColorKey.MakeArrayType(), new object[] { colorKeys.Length });
for (int i = 0; i < colorKeys.Length; ++i)
colorKeyParam.SetValue(Activator.CreateInstance(s_tyGradientColorKey, colorKeys[i].color, colorKeys[i].time), i);
s_piColorKeys.SetValue(_gradient, colorKeyParam, null);
}
if (alphaKeys != null) {
Array alphaKeyParam = (Array)Activator.CreateInstance(s_tyGradientAlphaKey.MakeArrayType(), new object[] { alphaKeys.Length });
for (int i = 0; i < alphaKeys.Length; ++i)
alphaKeyParam.SetValue(Activator.CreateInstance(s_tyGradientAlphaKey, alphaKeys[i].alpha, alphaKeys[i].time), i);
s_piAlphaKeys.SetValue(_gradient, alphaKeyParam, null);
}
}
#endregion
#else
#region Unity 4.x Implementation
private Gradient _gradient = new Gradient();
public object GradientData {
get { return _gradient; }
set { _gradient = value as Gradient; }
}
public Color Evaluate(float time) {
return _gradient.Evaluate(time);
}
public void SetKeys(ColorKey[] colorKeys, AlphaKey[] alphaKeys) {
GradientColorKey[] actualColorKeys = null;
GradientAlphaKey[] actualAlphaKeys = null;
if (colorKeys != null)
actualColorKeys = colorKeys.Select(key => new GradientColorKey(key.color, key.time)).ToArray();
if (alphaKeys != null)
actualAlphaKeys = alphaKeys.Select(key => new GradientAlphaKey(key.alpha, key.time)).ToArray();
_gradient.SetKeys(actualColorKeys, actualAlphaKeys);
}
#endregion
#endif
}
Assets/Editor/GradientTest/GradientEditorWindow.cs
using UnityEngine;
using UnityEditor;
public class GradientEditorWindow : EditorWindow {
[MenuItem("Window/Gradient Editor Test")]
public static void Show() {
GetWindow<GradientEditorWindow>("Gradient Editor Test Window");
}
GradientWrapper gradient;
float time;
GUIStyle previewStyle;
void OnEnable() {
previewStyle = new GUIStyle();
previewStyle.normal.background = EditorGUIUtility.whiteTexture;
}
void OnGUI() {
gradient = GUIGradientField.GradientField("Test Gradient", gradient);
if (GUILayout.Button("Set Keys Test")) {
OnSetKeysTest();
GUIUtility.ExitGUI();
}
time = Mathf.Clamp01(EditorGUILayout.Slider("Time", time, 0, 1));
// Extract color at specific time.
Color restoreBackgroundColor = GUI.backgroundColor;
GUI.backgroundColor = gradient.Evaluate(time);
GUILayout.Box(GUIContent.none, previewStyle, GUILayout.Width(200), GUILayout.Height(50));
GUI.backgroundColor = restoreBackgroundColor;
}
void OnSetKeysTest() {
GradientWrapper.ColorKey[] colorKeys = new GradientWrapper.ColorKey[] {
new GradientWrapper.ColorKey(Color.red, 0f),
new GradientWrapper.ColorKey(Color.green, 0.5f),
new GradientWrapper.ColorKey(Color.blue, 1f)
};
GradientWrapper.AlphaKey[] alphaKeys = new GradientWrapper.AlphaKey[] {
new GradientWrapper.AlphaKey(1f, 0f),
new GradientWrapper.AlphaKey(1f, 1f)
};
gradient.SetKeys(colorKeys, alphaKeys);
}
}
wow, thanks for the extensive reply! I can't look at it right now, but I'll look at it later today (I'm at work now, and this is for a hobby project)
Sorry for the late reply, the project I needed this for had been on hold for a while. But I have tested it and it works like a charm. I'm not familiar with reflection though, so I don't really know how it works, but I get a warning saying s_miGradientField2 isn't used (in GUIGradientField.cs). I commented it out and everything still works. Anyway, thanks a lot for this. :)
This was a cut and paste and it worked solution for me. Thanks!
Answer by ickydime · May 16, 2013 at 08:14 PM
I found a simpler way. At least less code.
Use EditorGUILayout.PropertyField and pass in a serializedProperty of a Gradient.
In my case I created a class that has a public gradient property. Example of some pseudo code below. Note, you will need to create a GradientContainer class that extends MonoBehaviour and has a public Gradient variable named 'ColorGradient'.
// OnEnable
GameObject tempObject = new GameObject();
GradientContainer gradientContainer = (GradientContainer) tempObject.AddComponent("GradientContainer");
SerializedObject serializedGradient = new SerializedObject(gradientContainer);
SerializedProperty colorGradient = serializedGradient.FindProperty("ColorGradient");
// OnGUI
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(colorGradient, true, null);
if(EditorGUI.EndChangeCheck())
{
serializedGradient.ApplyModifiedProperties();
}
// Accessing Later
GradientContainer gradientContainer = (GradientContainer)serializedGradient.targetObject;
Yes this is much simpler, though the example that I demonstrated will also work for recent versions of Unity 3.5 (which was the primary source of the complexity). But you are right, you propose a good solution for Unity 4 users :)
This worked for me. If you're making an Editor Window that doesn't need to remember variables, you can just use this
EditorGUI.BeginChangeCheck();
SerializedObject serializedGradient = new SerializedObject(this);
SerializedProperty colorGradient = serializedGradient.FindProperty("gradient");
EditorGUILayout.PropertyField(colorGradient, true, null);
if(EditorGUI.EndChangeCheck()) {
serializedGradient.Apply$$anonymous$$odifiedProperties();
}
You just saved my day! :)
PS: It makes NO sense that this is not the most correct answer to the question as it does exactly what you need...
Answer by darrinm · Apr 20, 2013 at 10:26 PM
To have the gradient editor appear for a property of your script, just declare it as:
C#:
public Gradient myGradient;
JS:
myGradient:Gradient;
Then you can access it via script as per the documentation you already found.
well yeah, but I was asking about the use in an Editor script. So I still need a GUI function to draw it in OnGUI()
This is great! I did notice however that the gradient does not actually serialize unless you have
[SerializeField] on private Gradient _gradient = new Gradient();
Answer by TheDreamMaster · Mar 12, 2015 at 05:27 AM
How exactly does one implement ickydime's solution?
I have tried to follow what he describes but have been unable to get the pseudocode to a usable state.
Answer by kiwon · Aug 18, 2017 at 02:08 AM
//using System.Linq //using System.Reflection
var method = typeof(EditorGUI)
.GetMethods(BindingFlags.NonPublic |BindingFlags.Static )
.First(t=>t.Name == "GradientField");
var change = m.Invoke(null, new object[] { rect, gradient });
it's working anyway.