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();
      		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);
      
    • 最后保存完毕记得删除场景上的物体

以下是完整代码

		[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(path);
	            GameObject obj =  PrefabUtility.InstantiatePrefab(objPrefab) as GameObject;
	            //判断是否存在于Hierarchy面板上
	            if (obj.hideFlags == HideFlags.None)
	            {
	                var components = obj.GetComponents();
	                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?我觉得这个可能性比较小
  • 最后要是有知道的好心人士希望可以告知一下,谢过~

有什么意见建议欢迎提出啦!-------来自一个刚刚开始工作的小萌新

你可能感兴趣的:(Unity辅助工具)