We really needed to have some code run as soon as a new scene was loaded in the editor. We’re making a 3D tile-based game and we calculate most of the data associated with them automatically from the GameObjects layout (such as the navigation links between them and debug display information). This is great as changing anything in the editor recalculates it, so we always know our data is good. However, we need to have that step of “Reclaculate Dynamic Stuff”. We use custom editor components to handle a lot of this, but again they don’t have any nice “scene loaded entry point”. We needed a solution.

So here’s the trick. Firstly you need to have an update function that is called all the time the editor is active. This is done by patching into EditorApplication.update. The easiest way to do this is using the very useful InitialiseOnLoad system that Unity provides. Here’s an example:

[InitializeOnLoad]
public class EditorCallBack
{
	static EditorCallBack ()
	{
		EditorApplication.update += Update;
	}
	static void Update ()
	{
	}	
}

When Unity starts up or you click play, Unity will hook into the update callback so you get a nice way to run things every frame. This can be useful for other things as well. I’ve seen one trick here where what you do is check to see if EditorApplication.currentScene changes, such as:

static string sSceneName = null;

static void Update ()
{
	if(sSceneName != EditorApplication.currentScene)
	{
		// New scene has been loaded
		sSceneName = EditorApplication.currentScene;
	}
}

This works great, apart from one case. What if you reload the same scene? We tried many things (mostly Unity based) to try and handle that case, but nothing worked. Then it hit us. You always tend to have certain classes setup in GameObjects, say a manager class you use in every scene. In our case we have a TileManager (which is also the class I need to tell about the new scene). So the trick is to get this class to track if it’s been setup.

public class TileManager
{
	static TileManager sInstance;

#if UNITY_EDITOR
	public static void CheckForEditorStartup()
	{
		if(sInstance == null)
			sInstance = UnityEngine.Object.FindObjectOfType();

		if(sInstance != null && sInstance.m_EditorInitialised == false)
		{
			HandleEditorStartup(null);
			sInstance.m_EditorInitialised = true;
		}
	}
#endif
}

You then need to call this method in the editor callback you previously set up:

static void Update ()
{
	TileManager.CheckForEditorStartup();
}

So essentially when a new scene loads, the static reference to TileManager is null. It should always be null as Unity’s garbage collection will reset it every time the class is unloaded. So the trick is now to simply find the instance of the class, then if it finds one check to see if m_EditorInitialised has been set. If it’s not, handle your initialisation then set it to true. Job done.

2 Comments:

  1. Jax February 17, 2015 Reply

    Nice! When do you remove the callback link though?

  2. Mikilo August 21, 2015 Reply

    Hello dudes!

    I stumbled upon this issue too and I wrote a solution.

    public static class EditorApplicationExtended
    {
    public static Action ChangeScene;

    private static string currentScene;
    private static int frameCount;
    private static float realtimeSinceStartup;
    private static int renderedFrameCount;

    static EditorApplicationExtended()
    {
    // Even if update is not the most elegant. Using hierarchyWindowChanged for CPU sake will not work in all cases, because when hierarchyWindowChanged is called, Time’s values might be all higher than current values. Why? Because current values are set at the first frame. If you keep reloading the same scene, this case happens.
    EditorApplication.update += EditorApplicationExtended.DetectChangeScene;
    }

    private static void DetectChangeScene()
    {
    if (EditorApplicationExtended.currentScene != EditorApplication.currentScene ||
    // Detect change with the same scene, the real time should fill 99% of cases. Not tested but if you are able to load 2 scenes in a single frame, the time might not work.
    EditorApplicationExtended.realtimeSinceStartup > Time.realtimeSinceStartup ||
    EditorApplicationExtended.frameCount > Time.frameCount ||
    EditorApplicationExtended.renderedFrameCount > Time.renderedFrameCount)
    {
    EditorApplicationExtended.currentScene = EditorApplication.currentScene;

    if (EditorApplicationExtended.ChangeScene != null)
    EditorApplicationExtended.ChangeScene();
    }

    EditorApplicationExtended.frameCount = Time.frameCount;
    EditorApplicationExtended.realtimeSinceStartup = Time.realtimeSinceStartup;
    EditorApplicationExtended.renderedFrameCount = Time.renderedFrameCount;
    }
    }

    That should work in all cases.

Leave a Comment!

Your email address will not be published. Required fields are marked *

Please prove you\'re human: *

Skip to toolbar