In our game Academia, we employ a multi-scene architecture. We now have hundreds of scenes that are all additively loaded at runtime to compose the whole game. Sometimes, there are fixes or new features that requires changes to all scenes. Imagine doing this manually. Load scene, apply change, save scene. Repeat to hundreds. This is outright untenable.

This is where an editor script would be handy and turns out to be quite easy. I encounter this problem many times that I’ve made a generic script for it. Here it goes:

public static class ScenesProcessor { private static readonly List<string> PATH_LIST = new List<string>(100); public static void Execute(string rootPath, string processName, Action<string> actionToExecute) { Debug.Log($"Processing: {rootPath}"); try { // Collect all scene files first PATH_LIST.Clear(); EditorUtility.DisplayProgressBar(processName, "Collecting scene paths to process. Please wait.", 0); CollectScenePaths(rootPath, PATH_LIST); int count = PATH_LIST.Count; for (int i = 0; i < count; ++i) { string scenePath = PATH_LIST[i]; EditorUtility.DisplayProgressBar(processName, $"Processing {scenePath}", ((i + 1) / (float)count)); // Invoke the action actionToExecute(scenePath); Debug.Log($"Processed {scenePath}"); } } finally { EditorUtility.ClearProgressBar(); } } private static void CollectScenePaths(string rootPath, List<string> pathList) { string[] files = Directory.GetFiles(rootPath); for (int i = 0; i < files.Length; ++i) { // Scenes end with .unity if (files[i].EndsWith(".unity")) { // It's a scene. We add it. pathList.Add(files[i]); } } // Recurse to child directories string[] directories = Directory.GetDirectories(rootPath); for (int i = 0; i < directories.Length; ++i) { CollectScenePaths(directories[i], pathList); } } }

The key method here is Execute() which accepts the root directory, the name of the process, and the action to execute for each scene. The name is used as a label to the progress bar that will be shown. The method collects all scene paths under the root directory then the specified action would be invoked for each of them.

As a sample usage, we have a bug where the UI is not displayed properly for ultrawide screens (21:9). The fix is to use Expand for the Screen Match Mode property of CanvasScaler component. It’s a simple fix but each screen or panel is implemented as a separate scene with its own canvas. Applying that to each scene would take so much time. The utility script can do this in a jiffy:

[MenuItem("Game/CanvasScaler/SetToExpand")] private static void SetToExpand() { string rootDir = Path.Combine(Application.dataPath, "Game/Scenes"); ScenesProcessor.Execute(rootDir, "Set CanvasScaler to Expand", delegate(string scenePath) { // Load the scene Scene openedScene = EditorSceneManager.OpenScene(scenePath); // Set to every CanvasScaler CanvasScaler[] components = Object.FindObjectsOfType<CanvasScaler>(); for (int i = 0; i < components.Length; ++i) { Debug.Log(components[i].gameObject.name); components[i].screenMatchMode = CanvasScaler.ScreenMatchMode.Expand; } // Save the scene EditorSceneManager.MarkSceneDirty(openedScene); EditorSceneManager.SaveOpenScenes(); }); }

When this is executed from the editor menu, it shows a nifty progress bar:

That’s all for now. Good luck on your project!