*Edit: Solution below in edited code.
////////////////////////////////////////////////////
I've been trying to incorporate undo functionality for my custom editor script for a few hours now, with no success.
I've tried all functions associated with the "undo" class, but I just cannot get it to work.
Can someone please explain the proper way to get a slider value tied into the undo system?
The (fixed) code I now have:
using UnityEditor;
using UnityEngine;
public class sliderTester2 : EditorWindow{
[SerializeField] float sliderValue = 1f;
[MenuItem("Slider Tester/ Slider Test v2")]
static void Init() {
sliderTester2 myWindow = (sliderTester2)EditorWindow.GetWindow(typeof(sliderTester2));
myWindow.autoRepaintOnSceneChange = true;
}
void OnGUI(){
checkMouse();
// Undo.SetSnapshotTarget(this, "Slider Value Changed");
GUILayout.BeginVertical("box");
GUILayout.Label("Test Slider");
sliderValue = EditorGUILayout.Slider(sliderValue, .1f, 5f);
if (GUI.changed){
// Undo.CreateSnapshot();
// Undo.RegisterSnapshot();
}
GUILayout.EndVertical();
Undo.ClearSnapshotTarget();
this.Repaint();
} // on GUI
void checkMouse(){
Event e = Event.current;
if (e.button == 0 && e.isMouse){
Debug.Log("Mouse down");
Undo.SetSnapshotTarget(this, "Changed Slider");
Undo.CreateSnapshot();
Undo.RegisterSnapshot();
}
}
} // class definition
From the reference:
Certain operations, such as dragging, consist of many small incremental changes. Typically it is not desired to create an undo step for each of these small changes. For example, if the user performs an undo after a dragging operation, it is expected that the object is reverted back to the state it had before the dragging started. The functions SetSnapshotTarget, CreateSnapshot, and RegisterSnapshot are available to handle cases like this.
I followed the links to get to this code. I tested it and it works, however for some strange reason I get imprecision for the value when I try to undo. 0.1 might become 0.123 et.c. Also, if you select the text box with the value, then drag the slider, you will be creating snapshots for every single frame for some reason. I tried to make the code so it only create a snapshot when the mouse button is released but I couldn't get any input code working (Neither Input.GetMouseButtonXxx nor Event.current.button worked for me)! If you know a way to handle this case you might get a better solution.
Edit: You might get this to work if you set `myWindow.wantsMouseMove = true;` in init, although I leave that for you to play around with. Note that the user can change the value both by using the slider and editing the text so it might get complex.
Anyhow, there's 5 changes to your code. See comments in code, they are also numbered for ease.
- You need your variables to be serializable. It must not be static and it must be either public or have the SerializeField attribute.
- (Edit, see below) Hook up with key modifiers to repaint our scene.
- Set this as being the current snapshot target.
- Create and Register snapshot when the GUI was changed. If possible, this code could benefit from an input test to only perform these operations when the user releases the mouse button.
- Clear the snapshot target at end of call. I don't know if this is necessary but that's how I did it anyway.
Worth noting is that it seems there is a slight delay from when you press CTRL + Z (Undo shortcut) to the actual value is undone. This doesn't seem to be the case if the text box is selected when you do this. I don't know why this happens. Maybe I am overlooking something or maybe it's some mechanic of UnityEditor that does this.
Edit: After further investigation it seems that OnGUI isn't called continously. It is only called when the user interacts with the GUI. This causes the lag inside the editor window since it isn't repainted. I fixed this by adding Repaint to key modifiers although it might not be the best way to go about. It solved this particlular case however and I've updated the code.
And the code follows:
using UnityEditor;
using UnityEngine;
public class sliderTester : EditorWindow
{
// 1.
// Drop the static and make it serializable
// since Undo snapshots serialize the object.
[SerializeField]
float sliderValue = 1f;
[MenuItem("Slider Tester/ Slider Test v1")]
static void Init()
{
sliderTester myWindow = (sliderTester)EditorWindow.GetWindow(typeof(sliderTester));
myWindow.autoRepaintOnSceneChange = true;
// 2.
// Since shortcut undo doesn't seem to update the window, we
// hook up to a delegate to do this ourselves.
EditorApplication.modifierKeysChanged += myWindow.Repaint;
}
void OnGUI()
{
// 3.
// Set this being the snapshot target.
Undo.SetSnapshotTarget(this, "Changed Settings");
GUILayout.BeginVertical("box");
GUILayout.Label("Test Slider");
sliderValue = EditorGUILayout.Slider(sliderValue, .1f, 5f);
// 4.
// Handle snapshots when gui changes.
if (GUI.changed)
{
Undo.CreateSnapshot();
Undo.RegisterSnapshot();
}
GUILayout.EndVertical();
// 5. (Not sure if needed)
// Clear the snapshot at end of call.
Undo.ClearSnapshotTarget();
} // on GUI
} // class definition