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:

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

5 Answers

5

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.

[10291-screenyt.png*_|10291]

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 :slight_smile:

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_.color, colorKeys*.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_.alpha, alphaKeys.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(“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!

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.ApplyModifiedProperties(); }

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();

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.

//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.