批量删除预制脚本

环境:Unity 2018.4.12

需求:

项目中经常需要在Unity Editor模式下批量删除Prefab中的某个脚本,但是直接用`GameObject.DestroyImmediate(obj);`会报错:Destroying assets is not permitted to avoid data loss.解决方法是通过`SerializedProperty.DeleteArrayElementAtIndex(i)`。

代码:

private bool RemoveComponents(Component[] components)
        {
            if (components.Length == 0)
            {
                return false;
            }
            foreach (var component in components)
            {
                SerializedObject so = new SerializedObject(component.gameObject);
                SerializedProperty sp = so.FindProperty("m_Component");

                var allComponents = component.gameObject.GetComponents<Component>();
                for (int i = 0, len = allComponents.Length; i < len; i++)
                {
                    if (allComponents[i] == component)
                    {
                        sp.DeleteArrayElementAtIndex(i);
                        so.ApplyModifiedProperties();
                        break;
                    }
                }
            }

            return true;
        }
public  bool RemoveComponents<T>(GameObject root) where T : Component
        {
            if (root == null)
            {
                return false;
            }
            var components = root.GetComponentsInChildren<T>(true);
            return RemoveComponents(components);
        }

工具

为了方便使用,实现一个EditorWindow来操作,代码如下:
using UnityEditor;
using UnityEngine;

namespace Common.Editor.Common
{
    public class RemoveComponentEditor : EditorWindow
    {
        private static RemoveComponentEditor _instance;
        private static string[] _selectAssets;
        private static string _key;
        private static MonoScript _monoScript;
        
        [MenuItem("PrefabTool/RemoveComponentEditor")]
        public static void ShowWindow()
        {
            _instance = GetWindow(typeof(RemoveComponentEditor)) as RemoveComponentEditor;
            _instance.Show();
        }
        
        void OnGUI()
        {
            _selectAssets = Selection.assetGUIDs;
            if (_selectAssets == null || _selectAssets.Length == 0)
            {
                EditorGUILayout.LabelField("No Assets Selected");
            }
            else
            {
                EditorGUILayout.LabelField($"Selected Assets:\n");
                foreach (var asset in _selectAssets)
                {
                    var path = AssetDatabase.GUIDToAssetPath(asset);
                    EditorGUILayout.LabelField($"{path}\n");
                }
            }

            _key = EditorGUILayout.TextField("Prefab Name Key:", _key);
            _monoScript = EditorGUILayout.ObjectField("Script", _monoScript, typeof(MonoScript), false) as MonoScript;
            if (GUILayout.Button("Remove"))
            {
                Remove();
            }
        }

        private void Remove()
        {
            if (_selectAssets == null || _selectAssets.Length == 0)
            {
                EditorUtility.DisplayDialog("Error", "Please Select A Folder Or Asset", "OK");
                return;
            }

            if (_monoScript == null)
            {
                EditorUtility.DisplayDialog("Error", "Please Select A Mono Script", "OK");
                return;
            }

            var prefabs = CommonToolUtil.GetSelectPrefabs(_key);
            CommonToolUtil.ExecutePrefabs(prefabs, Execute, "Execute", true);
        }
        
        private bool Execute(GameObject prefab)
        {
            return CommonToolUtil.RemoveComponents(prefab, _monoScript);
        }
    }
}

GetSelectPrefabs()函数用来获得选中目录下的所有Prefabs,参数key用来过滤Prefab的名字,因为一个文件夹下可能有多个采用不同前后缀命名的Variant:

public static List<GameObject> GetSelectPrefabs(string key)
        {
            List<GameObject> list = new List<GameObject>();
            Object[] selectedAssets = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
            foreach (Object asset in selectedAssets)
            {
                string filePath = AssetDatabase.GetAssetPath(asset);
                if (!filePath.EndsWith(".prefab"))
                {
                    continue;
                }

                if (!string.IsNullOrEmpty(key))
                {
                    var fileName = Path.GetFileNameWithoutExtension(filePath);
                    if (!fileName.Contains(key))
                    {
                        continue;
                    }
                }
                
                var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(filePath);
                if (prefab == null)
                {
                    continue;
                }
                list.Add(prefab);
            }
            return list;
        }

ExecutePrefabs函数遍历Prefabs,并显示进度条,有修改则保存

public static void ExecutePrefabs(List<GameObject> prefabs, Func<GameObject, bool> action, string content, bool save = true)
        {
            var count = prefabs.Count;
            var index = 0;
            foreach (var prefab in prefabs)
            {
                EditorUtility.DisplayProgressBar(content,
                    $"{content}...{++index}/{count}", (float) index / count);
                bool result = action.Invoke(prefab);
                if (save && result)
                {
                    PrefabUtility.SavePrefabAsset(prefab);
                }
            }

            if (save)
            {
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }
            
            EditorUtility.ClearProgressBar();
        }

RemoveComponents函数通过MonoScript获得Component数组,调用上面的方法移除组件

public static bool RemoveComponents(GameObject root, MonoScript monoScript)
        {
            if (root == null || monoScript == null)
            {
                return false;
            }

            var type = monoScript.GetClass();
            if (type == null)
            {
                return false;
            }

            var components = root.GetComponentsInChildren(type);
            return RemoveComponents(components);
        }

运行效果如图:
可以选择目录或预制,设置关键词过滤,选择要删除的组件
Editor

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值