【Unity】フォルダ内にある全ての Scene, Prefab 内から条件に合う GameObject を一括で編集する関数

# 概要

AssetDatabase.FindAssets() を再帰的に使い、選択したフォルダ内の全ての Asset を取得する関数を作りました。
また、選択したフォルダ内の全ての Scene, Prefab 内から条件に合う GameObject を探し、まとめて一度に編集できる関数を作りました。

### 留意点

フォルダ内に Scene がある際は現在の Sence を保存し、対象の Scene を開きます。全てのファイルを探し終えたら、開始時に開いていた Scene を開きます。
一括変換時にはログを表示して、想定外のオブジェクトが変更されていないか確認できるようにしておくことをお勧めします。

# 利用例

- 選択したフォルダ内の全 Scene, Prefab の AudioSource の DopplerLevel を 0 にする
- 選択したフォルダ内の全 Scene, Prefab の Button の Navigation.Mode を None にする

``` C#
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;

namespace Assets.Editor
{
    public class EditorUtilSample
    {
        /// <summary>
        /// 選択したフォルダ内の全 Scene, Prefab の AudioSource の DopplerLevel を 0 にする
        /// </summary>
        [MenuItem("Tools/" + nameof(DisableDoppler))]
        private static void DisableDoppler()
        {
            EditorUtil.EditSelectionGameObjects(o => o.TryGetComponent<AudioSource>(out var _), o =>
            {
                o.TryGetComponent<AudioSource>(out var audioSource);
                audioSource.dopplerLevel = 0;
            });
        }
        /// <summary>
        /// 選択したフォルダ内の全 Scene, Prefab の Button の Navigation.Mode を None にする
        /// </summary>
        [MenuItem("Tools/" + nameof(DisableButtonNavigation))]
        private static void DisableButtonNavigation()
        {
            EditorUtil.EditSelectionGameObjects(o => o.TryGetComponent<Button>(out var _), o =>
            {
                o.TryGetComponent<Button>(out var button);
                var n = new Navigation();
                n.mode = Navigation.Mode.None;
                button.navigation = n;
            });
        }
    }
}
#endif
```

# コード

お手持ちの EditorUtil クラスに追加してご利用ください。

``` EditorUtil.cs
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Assets.Editor
{
    public static class EditorUtil
    {
        public static List<T> GetSelectionAssets<T>(string findAssetsFilter)
        {
            var list = new List<T>();
            foreach (var obj in Selection.objects) GetAssets(findAssetsFilter, ref list, obj);
            return list;
        }

        public static void GetAssets<T>(string findAssetsFilter, ref List<T> list, Object obj)
        {
            if (obj == null) return;
            if (obj is T t) list.Add(t);
            var path = AssetDatabase.GetAssetPath(obj);
            if (string.IsNullOrEmpty(path) || File.GetAttributes(path).HasFlag(FileAttributes.Directory) == false) return;

            foreach (var childPath in AssetDatabase.FindAssets(findAssetsFilter, new[] { path }).Select(AssetDatabase.GUIDToAssetPath))
            {
                GetAssets(findAssetsFilter, ref list, AssetDatabase.LoadAssetAtPath<Object>(childPath));
            }
        }

        #region EditGameObjects
        public class EditGameObjectsConfig
        {
            public string FindAssetsFilter;
            public Func<GameObject, bool> Predicate;
            public Action<GameObject> EditProcess;
            public HashSet<Object> Passed = new();
            public bool IsSavedCurrentScene = false;
            public bool IsEditPrefab = true;
            public bool IsEditScene = true;
        }

        public static void EditSelectionGameObjects(Func<GameObject, bool> predicate, Action<GameObject> editProcess, List<Object> ignoreObjects = null, string findAssetsFilter = null)
            => EditGameObjects(Selection.objects, predicate, editProcess, ignoreObjects, findAssetsFilter);

        public static void EditGameObjects(IEnumerable<Object> objects, Func<GameObject, bool> predicate, Action<GameObject> editProcess, List<Object> ignoreObjects = null, string findAssetsFilter = null)
        {
            var config = new EditGameObjectsConfig();
            config.FindAssetsFilter = findAssetsFilter;
            config.Predicate = predicate;
            config.EditProcess = editProcess;
            if (ignoreObjects != null) foreach (var o in ignoreObjects) config.Passed.Add(o);

            EditGameObjects(objects, config);
        }
        public static void EditGameObjects(IEnumerable<Object> objects, EditGameObjectsConfig config)
        {
            var currentScenePath = UnityEngine.SceneManagement.SceneManager.GetActiveScene().path;
            foreach (var obj in objects) EditGameObjectsFindAssets(ref config, obj);
            if (config.IsSavedCurrentScene)
            {
                EditorSceneManager.SaveOpenScenes();
                EditorSceneManager.OpenScene(currentScenePath);
            }
        }

        private static void EditGameObjectsFindAssets(ref EditGameObjectsConfig config, Object obj)
        {
            if (obj == null || config.Passed.TryGetValue(obj, out _)) return;
            config.Passed.Add(obj);

            if (config.IsEditPrefab && obj is GameObject)
            {
                EditGameObjectsPrefab(ref config, obj);
                return;
            }
            if (config.IsEditScene && obj is SceneAsset)
            {
                EditGameObjectsScene(ref config, obj);
                return;
            }

            var path = AssetDatabase.GetAssetPath(obj);
            if (string.IsNullOrEmpty(path) || File.GetAttributes(path).HasFlag(FileAttributes.Directory) == false) return;

            foreach (var childPath in AssetDatabase.FindAssets(config.FindAssetsFilter, new[] { path }).Select(AssetDatabase.GUIDToAssetPath))
            {
                EditGameObjectsFindAssets(ref config, AssetDatabase.LoadAssetAtPath<Object>(childPath));
            }
        }
        private static void EditGameObjectsPrefab(ref EditGameObjectsConfig config, Object obj)
        {
            var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj);
            var extension = Path.GetExtension(path);
            if (extension != ".prefab" && extension != ".unity") return;

            GameObject gameObject;
            gameObject = PrefabUtility.LoadPrefabContents(path);
            try { gameObject = PrefabUtility.LoadPrefabContents(path); }
            catch
            {
                Debug.Log("Skipped prefabs that could not be loaded : " + path);
                return;
            }

            foreach (var transform in gameObject.GetComponentsInChildren<Transform>(true))
            {
                var prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(transform.gameObject);
                if (prefabObject != null && prefabObject != gameObject) EditGameObjectsFindAssets(ref config, prefabObject);
            }

            var editFlag = false;
            foreach (var transform in gameObject.GetComponentsInChildren<Transform>(true))
            {
                if (config.Predicate(transform.gameObject) == false) continue;
                config.EditProcess(transform.gameObject);
                editFlag = true;
            }
            if (editFlag == false)
            {
                PrefabUtility.UnloadPrefabContents(gameObject);
                return;
            }

            try { PrefabUtility.SaveAsPrefabAsset(gameObject, path); }
            catch { Debug.Log("Failed to save prefab : " + path); }
            PrefabUtility.UnloadPrefabContents(gameObject);
            AssetDatabase.Refresh();
        }
        private static void EditGameObjectsScene(ref EditGameObjectsConfig config, Object obj)
        {
            config.IsSavedCurrentScene = true;
            EditorSceneManager.SaveOpenScenes();

            var path = AssetDatabase.GetAssetPath(obj);
            EditorSceneManager.OpenScene(path);

            foreach (var o in Object.FindObjectsOfType(typeof(GameObject)) as GameObject[])
            {
                var prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(o);
                if (prefabObject != null && prefabObject != o) EditGameObjectsFindAssets(ref config, o);
            }

            foreach (var o in Object.FindObjectsOfType(typeof(GameObject)) as GameObject[])
            {
                if (config.Predicate(o))
                {
                    config.EditProcess(o);
                    EditorUtility.SetDirty(o);
                }
            }
        }
        #endregion
    }
}
#endif
```