Unity:一键移除所有预制体上的Missing脚本

前言
开始工作一段时间后,发现有时经常会出现删了脚本缺漏删预制体上的引用,导致项目内出现了含有missing脚本的预制体,造成了一些影响,因此就做了一个小功能,一键清除预制体上的minssing脚本

整理与尝试过后的思路:
拿到所有的预制体进行遍历
将预制体生成至场景中
将预制体序列化得到身上的component序列化信息 (尝试过正常的销毁组件方法不可行,至于原因,还在探寻)
遍历检查,移除为空的component对应的序列化信息
保存修改过的序列化属性 (这个很重要,一定要记得保存)
将修改后的物体覆盖替换原预制体
最后记得删除场景中生成的物体 (否则,如果你的项目够大,你会看到很美妙的场景)
接下来是正题内容
第一步: 拿到所有的预制体进行遍历:这个很简单,有两种方法
通过IO去读取项目文件夹内的信息
Directory是IO中关于目录操作的静态类
GetFiles方法参数:(搜索起始路径,匹配内容,SearchOption枚举有两个类型:只搜索顶级目录和搜索全目录)
备注:通过这个方法得到的路径是绝对路径,在下面使用时要转换成相对路径,可直接使用这个方法转换: filePaths.Replace(Application.dataPath, “Assets”)
    
        string[] filePaths = 
            Directory.GetFiles(Application.dataPath, "*.prefab", SearchOption.AllDirectories);

通过Unity的编辑器管理类AssetDatabase
要注意,AssetDataBase.FindAssets方法返回的是一串guid,因此还需要使用GUIDToAssetPath得到对应的路径
备注:这里的路径就是相对路径,可以直接使用
        string[] fileGuids = AssetDatabase.FindAssets("t:Prefab");
        for(int i = 0 ; i < fileGuids .Length ; i++)
        {
            string path = AssetDatabase.GUIDToAssetPath(fileGuids[i]); 
            //后续操作
            //....
        }

之后用AssetDatabase.LoadAssetAtPath(path);方法拿到预制体的引用就可以啦。
第二步: 将预制生成至场景中
以下两种方法都可以实现所需效果
他们的区别在于PrefabUtility生成的物体会与预制体保持关联,而GameObject不会。后者一般用于运行时
        PrefabUtility.InstantiatePrefab(prefab);
        GameObject.Instantiate(prefab);
第三步: 序列化生成的物体得到component的序列化信息数组 并进行判空删除
遍历物体上的Component,当遇到空Component时,删除对应下标的序列化信息
SerializedObject .FindProperty 这个实例方法返回的是一个序列化数据中的某个数据的引用
SerializedObject.DeleteArrayElementAtIndex 这个就是根据下标删除序列化属性中对应内容的实例方法
注意:每次删除的下标要减去一个已删除个数的偏移,因为删除序列化数组的某个信息后,后面的会自动前移,他们的下标就都会减小
提示:删除的时候加个Log信息打印处理的内容,对使用者更友好
        var components = obj.GetComponents<Component>();
        SerializedObject so=new SerializedObject(obj);
        var soProperties = so.FindProperty("m_Component");
        int r = 0;
        for (int j = 0; j < components.Length; j++)
        { 
            if (components[j] == null)
            {
                soProperties.DeleteArrayElementAtIndex(j-r);
                Debug.LogError("清除了物体:"+obj.name +" 的一个missing脚本");
                r++;
            }
        }

第四步: 保存修改后的序列化信息,覆盖原预制体,删除场景中的物体
SerializedObject .ApplyModifiedProperties()这个方法必须记得使用,他会将修改后的序列化数据保存回原物体中
在Unity2018.4版本以前并没有PrefabUtility.SaveAsPrefabAssetAndConnect 而是用PrefabUtility.ReplacePrefab 来实现类似的功能
        so.ApplyModifiedProperties();
        PrefabUtility.SaveAsPrefabAssetAndConnect(obj, path, InteractionMode.AutomatedAction);
1
2
最后保存完毕记得删除场景上的物体
以下是完整代码
        [MenuItem("Test/CleanMissingScript")]
        public static void Begin()
        {
            string[] filePaths = Directory.GetFiles(Application.dataPath, "*.prefab", SearchOption.AllDirectories);
            int sum = 0;
            for (int i = 0; i < filePaths.Length; i++)
            {
                string path = filePaths[i].Replace(Application.dataPath, "Assets");
                GameObject objPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                GameObject obj =  PrefabUtility.InstantiatePrefab(objPrefab) as GameObject;
                //判断是否存在于Hierarchy面板上
                if (obj.hideFlags == HideFlags.None)
                {
                    var components = obj.GetComponents<Component>();
                    SerializedObject so=new SerializedObject(obj);
                    var soProperties = so.FindProperty("m_Component");
                    int r = 0;
                    for (int j = 0; j < components.Length; j++)
                    { 
                        if (components[j] == null)
                        {
                            soProperties.DeleteArrayElementAtIndex(j-r);
                            Debug.LogError("清除了物体:"+obj.name +" 的一个missing脚本");
                            r++;
                        }
                    }
                    if (r > 0)
                    {
                        so.ApplyModifiedProperties();
                        PrefabUtility.SaveAsPrefabAssetAndConnect(obj, path, InteractionMode.AutomatedAction);
                        AssetDatabase.Refresh();
                    }
                    sum += r;
                    UnityEngine.Object.DestroyImmediate(obj);
                }
    
            }
            Debug.LogError("清除完成,清理个数:"+sum);
        }

最后
在最新的2019.2.3版本中,DeleteArrayElementAtIndex方法删除序列化数据似乎出现了问题,无论如何都不允许删除数据,会给出错误提醒。2018版本是没有问题的

可能是2019版本哪里发现了变动,我需要做对应操作才能解锁使允许删除数据,我还在琢磨
可能是2019版本这部分出现了bug?我觉得这个可能性比较小
最后要是有知道的好心人士希望可以告知一下,谢过~
————————————————
版权声明:本文为CSDN博主「小温小温美梦成真」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35207836/article/details/100165414

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值