(一)问题

在我们做项目的过程中会涉及到很多文件操作。到了项目后期,项目文件夹会非常庞大。有时候我们需要对某一类文件做出处理,如果我们手动对给定类型的每个文件进行操作的话会非常的耗时,而且不能保证准确率。因此我们往往需要一些小工具帮我们进行这种批处理工作。

假设我们需要遍历某个工程文件夹中的所有prefab,如果prefab下有名为Target的组件,且这个组件下名为Flag的bool类型的字段值为true的话,输出文件路径。


(二)分析

其实这个问题可以分解为三个问题:

(1)获取和遍历给定目录下所有文件的方法;

(2)处理工作(具体问题具体分析,这里给出一个例子);

(3)将函数嵌入编辑器,并且进行一些过程的优化。              

之前在读一本叫做《游戏架构核心技术与面试精粹》的书的时候,曾经简单了解过对Unity工程文件进行批处理的一些操作,感兴趣的朋友们可以了解一下这本书。另外《Unity脚本设计》一书中对Unity编辑器菜单栏的嵌入也有描述。


(三)解决

关于遍历文件夹下中给定类型文件的方法在另外一篇对Excel文件进行解析的博客中有相关代码,在这里不多赘述。主要是引用了System.IO然后使用Application.dataPath获取当前项目目录,再使用GetFiles方法获取目录下的文件。

注意:有时候在UnityEditor中使用Application.dataPath获得正确结果但是在导出的项目中却不能正确读取到文件。这是因为Application.dataPath获取的是当前路径,项目路径和可执行程序路径是不一样的,并且其文件夹的结构也是不一样的。比如试图获取Assets下的Test文件夹中的文件,这在Editor中操作完全没有问题,但是在导出可执行程序的时候却会出现错误。这是因为导出的程序中可能并没有Test文件夹。因为这里我们是单纯的编辑器中的辅助函数,所以不考虑这个问题。

在加载项目文件夹中的资源的时候,Unity给我们提供了很多方法。比如Resources.Load方法、使用AssetBundle加载、使用AssetDatabase加载。其中AssetDatabase方法供程序员在编辑器中对资源文件进行管理,只能在编辑器中使用,其加载速度快、效率高。而另外两个方法主要是供程序运行的时候进行调用。很显然,本问题适合用AssetDatabase进行处理。

处理本问题一个很直接的想法就是直接使用AssetDatabase.LoadAssetAtPath获取目标路径下的所有Prafab,再获取GameObject下的Target组件,判断Flag是否为真。但是当直接使用GetComponent<Target>()去获取Target组件的时候,无论如何获取的结果都为null。后来修改为了GetComponents获取全部组件,再逐个遍历组件是否为Target,这样的确能够达到效果,但是不够优雅。

后来知道了AssetDatabase.LoadAssetAtPath可以直接获取组件信息,那我们就可以直接使用AssetDatabase.LoadAssetAtPath<Target>(assetPath)加载组件了。到这里为止,函数核心部分就完成了。

嵌入Unity窗口中也很简单,只需要在函数前加上[MenuItem("Test/查找指定的prefab")]就可以了。这样,我们就能在Unity编辑器中点击“Test”→“查找指定prefab”来直接在编辑器中调用这个函数。此处在函数执行过程中使用了一个ProgressBar来展示文件处理进度。Unity在运行这个函数的时候是完全不能交互的,如果没有进度条提示进度显示当前工作的话,整个Unity会处于一种类似卡死的状态,实际上Unity是在勤勉工作啦。整体代码如下:

public class PrefabScanner
{
    const string TEST_FOLDER = "Assets/Test";

    [MenuItem("Test/查找指定的prefab")]
    public static void SearchTargetPrefab()
    {      
        var assetRootPath = Application.dataPath;
        //Debug.log(assetRootPath);
        var dirPath = string.Format("{0}{1}", assetRootPath.Replace("Assets", ""), TEST_FOLDER);
        //Debug.log(dirPath);
        var prefabFiles = Directory.GetFiles(dirPath, "*.prefab");
        int count = 0;

        foreach(var prefabPath in prefabFiles)
        {
            count++;
            var assetPath = prefabPath.Substring(prefabPath.IndexOf("Assets/"));

            var targetComponent = AssetDatabase.LoadAssetAtPath<Target>(assetPath);
            if (targetComponent != null && targetComponent.flag)
                Debug.Log(assetPath);           
            EditorUtility.DisplayProgressBar("进度", assetPath, 1f * count / prefabFiles.Length);
        }
        EditorUtility.ClearProgressBar();            
    }
}