Process All Scenes in a Folder

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!

Leave a comment