Unity: Live Preview in the Editor

Running Code on Update outside of Play mode

If you ever wanted to create your own Animator, Particle or Tweening system in Unity and want to provide immediate visual feedback or a preview inside of the Editor, without the need to…:

  1. make changes

  2. hit Play

  3. inspect the results

  4. exit Play Mode

  5. repeat step 1

…you can benefit from this neat little trick that I'm going to show you, as well as show some of the minor limitations that exist in doing so.

The basic idea is that you subscribe to the Update() function of the EditorWindow class and call your own Update() function of your CustomInspector script with it. This allows us to have something like creating custom particle GameObject's dynamically, have movement in your scene, update a stats window in the scene, or anything else you could normally do in Play Mode, just outside of it.

The main drawbacks are a limited frame rate (the Editor doesn't update at the same frame rate as in-game and the complexity of other editor windows might degrade that even further, but by default, it still allows for quite smooth operations), a lot of the in-game optimizations are missing, physics aren't being calculated accurately or at all and immediate changes to the scene can't be easily reverted (eg. creating or altering lots of GameObjects).

With these things in mind, using this approach should ideally only be used for fail-safe preview functionalities and you should test your feature outside of the custom Update() function before putting it in there, to prevent something like infinite loops or creating too many complex objects that reduce the Editor's performance or make it crash.

But enough with the preliminary skirmishing, let's have look at an actual code example and how I implemented the editor preview for my custom NGUI animation system NGUIToolbox:

public class UIAnimatorWindow : EditorWindow
{
  private void OnEnable()
  {
    //subscribe to the Editor's Update() function to smooth out the preview animation by continuously updating
    EditorApplication.update += Update;
  }

  private void OnDisable()
  {
    //unsubscribe from the Update function when closing the window to prevent memory leaks
    EditorApplication.update -= Update;
  }

  private void Update()
  {
    //decrease the alpha of the focusArea-outline
    if (focusOutlineAlpha > 0) {
      focusOutlineAlpha -= 0.005f;
      Repaint();
    }

    if (!play) return; //only update the following when playing the animation

    passedTime = Time.realtimeSinceStartup - startTime;
    frametime = 1f / frameRate;

    if (anim.Count < 2) play = false;
    else if (passedTime >= frametime)
    {
      //select next frame
      if (reverse)
      {
        if (!loop && currSelFrameID == 0) play = false;
        currSelFrameID = currSelFrameID > 0 ? currSelFrameID - 1 : anim.Count - 1;
      }
      else
      {
        if (!loop && currSelFrameID == anim.Count - 1) play = false;
        currSelFrameID = currSelFrameID < anim.Count - 1 ? currSelFrameID + 1 : 0;
      }

      startTime = Time.realtimeSinceStartup;
      SelectFrame(currSelFrameID);
      Repaint();
    }
  }

  private void OnGUI()
  {
    //…your usual GUI generating code…
  }
}

The content of the Update() function in this example allows us not only to update the preview when it's set to play and has at least 2 frames but also reduce the alpha value for a particular selection outline effect (showing the outline with full opacity when selecting an item and smoothly fading it out shortly after). This shows us the power of this approach, as OnGUI() would only update on every user interaction (mouse movement, text input, screen resizing, and so on).

As mentioned before, you are not limited by updating things inside your Editor window or custom inspector, but can also manipulate everything in your scene as you could inside of the actual Play mode.

Fri May 08 2020

Comments