An automated scene-loading menu in the Unity editor

~ written 27 September 2021 ~

One common papercut that I've seen affecting both experienced and beginner Unity users is the ostensibly-simple act of loading a different scene in the editor. Unity treats a scene file the same as any other asset: in order to open a new scene for editing, you first have to find it within the Project window and double-click it. That's quick enough in a simple project, but on Goodbye Volcano High, we've got hundreds of folders and several different scene types (main, minigame, non-gameplay, test, and... others). Burrowing down through several folder hierarchies for the right one can become a tedious, confusing distraction, and runs the frustrating risk of losing your Inspector focus (if not your own mental focus).

On our previous game, Winding Worlds, we had a small script to add a scene-loading menu to Unity's editor interface. Now, instead of digging through our project hierarchy, we could jump directly into a scene in two clicks. It seemed like a really small change at first, but the more we used it, the more we appreciated the simplified workflow: we hadn't realised we were being tripped up until we weren't.

Willow appreciates the simplicity of the editor scene-loading menu in Winding Worlds.
Willow appreciates the simplicity of the editor scene-loading menu in Winding Worlds.

The code for that menu: tediously, repetitively, & manually generated.
The code for that menu: tediously, repetitively, & manually generated.

This new scene-selection menu was great, but it had to be manually populated: that is, a programmer would have to create an editor script with a [MenuItem] for each new scene as it was added, and then continue maintenance on it as scenes were moved, renamed, or deleted. You can see a portion of that code in the image above; note that it's pretty repetitive, with one method for every scene we want in our menu. On Winding Worlds, this wasn't too big a problem: the game has ten main levels, and two or three "meta" scenes (such as the splash screen).

GVH has an order of magnitude more scenes, however, so a manual approach wasn't desirable or sustainable. We all agreed this was a good candidate for some editor scripting, but there was (as always) a little hiccup. It's easy enough to grab a list of, say, all scene files in a given directory:
AssetDatabase.FindAssets("t:Scene", new string[] { SCENE_DIR });
However, the [MenuItem] entries have to be already defined at compile time in order to be turned into a Unity menu. It felt like I had to... run editor code (to get the list of scenes) before the editor code was compiled (to generate the actual UI menu items). It seemed like a deadlock.

Luckily, my coworker Hazel suggested a wonderfully straightforward solution: if our editor scripts are already creating and editing art files (we do a lot of pre- and post-processing of Photoshop images and Spine animations), why not make an editor script that creates and edits another editor script?

In this configuration, we have two files: the writer script, and the generated script. First, the writer gets the list of scenes in a given directory. It then creates a new file and writes a C# script into that file. This includes some C# boilerplate, along with one menu item for every scene we want to display in our editor menu (and you can add your own logic here: ignoring test scenes, &c.). And that's it – once the auto-generated file is compiled anew, it generates the Unity editor menu we wanted, automatically-populated with all the scenes in our game, one click away. Check out the code & a little example gif at the end of this post.

We've been using editor scripts to create, parse, and modify asset files for so long, but it had never occurred to me to use an editor script to create another editor script. Doing so allowed us to automate the tedious & error-prone task of creating a scene-loading menu in the Unity editor, and ultimately relieved us of a hardly-perceptible but not insignificant frustration.


Some notes

Since the writer script is given the [InitializeOnLoad] attribute, it's run every time the Unity editor is launched, as well as on every domain reload. In theory, if you created a new scene and looked for it in the menu right away, you wouldn't find it; but in practice, the creation of new scenes in a large project is rare enough, and domain reloads frequent enough, that this shouldn't be a problem.

I'll also note that if you're using a versioning system, you should consider adding the generated file to your equivalent of .ignore, since it's created automatically anyway.


The code

The dynamically-generated scene-loading menu in action in a test project
The dynamically-generated scene-loading menu in action in a test project.

x