【插件】无效Material文件检测工具

2 篇文章 0 订阅

之前分享了一篇《特效发射粒子数查找工具》,不知道大家还有没有印象。今天我再沿用此方式开发一个自定义的实用小工具,为我们之后的开发提供更多的帮助。

随着游戏项目的开发,需求不断的变更,资源不断的改版,项目中难免会预留大量重复的或过时的资源文件,导致我们在查找定位某个资源文件时,依旧在大量的无用文件中筛选,减缓了工作效率。有些强迫症人士更是忍受不了大量的无用文件存在项目中,想一一清理出去。但是我们手动去清理某个无用文件时,有时不能确保它是否真的就是“无用”,也许它依旧关联在某个场景或物体上。所以我今天以清理Material文件为例,做一个“无效Material文件检测工具”。

首先,我们还是先整理一下思路再上代码:

  1. 获取并显示我们在Project视图中选择的物体(可以是任何物体,不过暂时只对Material进行处理,未来可以拓展处理其他物体)
  2. 遍历需要检测的物体路径,目前分为三个检测点:场景、特效、角色
  3. 因为存在一个Material关联多个物体,所以要用Dictionary<string,List<XXX>>的形式存储数据,分类显示
  4. 如果检测到就显示出Material和关联物体的相关信息,没有检测到就标注红色并伴有“删除”按钮以供删除。

具体代码如下:

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System.IO;

public class MaterialReferenceChecker : EditorWindow
{
    Dictionary<string, List<MatDetail>> dictMat = new Dictionary<string, List<MatDetail>>();
    int texWidth = 40;
    int texHeight = 40;
    Vector2 vec2 = new Vector2(0, 0);
    bool isCheck = false;
    bool isCheckScene = true;
    bool isCheckEffect = true;
    bool isCheckCharacter = true;
    GameObject effectObj = null;
    GameObject characterObj = null;
    Object[] objs;

    [MenuItem("Tools/Check Material References")]
    public static void Init()
    {
        MaterialReferenceChecker window = GetWindow<MaterialReferenceChecker>();
        window.Show();
    }

    void OnGUI()
    {
        if (!isCheck)
        {
            if (objs != null && objs.Length > 100)  //防止选择太多物体,运算量太大
            {
                GUILayout.Label("你一次性选择太多资源(超过100个)!请重新选择资源,再点击“刷新”");
                if (GUILayout.Button("刷新"))
                {
                    objs = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
                }
                return;
            }
            objs = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
            GUILayout.BeginHorizontal();
            if (objs == null || objs.Length <= 0)
            {
                GUILayout.Label("你没有选择任何物体,请在Project选择,按住Ctrl可以多选");
                return;
            }
            else
            {
                GUILayout.Label("你选择了以下物体:共(" + objs.Length + ")个");
            }
            GUILayout.EndHorizontal();
            vec2 = GUILayout.BeginScrollView(vec2);
            for (int i = 0; i < objs.Length; i++)
            {
                ListSelections(objs[i]);
            }
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("开始查找关联"))
            {
                OnCheckReferences();
                isCheck = true;
            }
            isCheckScene = GUILayout.Toggle(isCheckScene, "遍历场景", GUILayout.Width(70));
            isCheckEffect = GUILayout.Toggle(isCheckEffect, "遍历特效", GUILayout.Width(70));
            isCheckCharacter = GUILayout.Toggle(isCheckCharacter, "遍历角色", GUILayout.Width(70));
            GUILayout.EndHorizontal();
            ListMaterials();
            GUILayout.EndScrollView();
        }
        else
        {
            //刷新数据,必须刷新才能重新查找
            vec2 = GUILayout.BeginScrollView(vec2);
            for (int i = 0; i < objs.Length; i++)
            {
                ListSelections(objs[i]);
            }
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("刷新"))
            {
                isCheck = false;
                dictMat.Clear();
            }
            GUILayout.EndHorizontal();
            ListMaterials();
            GUILayout.EndScrollView();
        }
    }

    //选中物体列表
    void ListSelections(Object selection)
    {
        GUILayout.BeginHorizontal();
        if (selection is Material || selection is Texture)
        {
            Texture tex = null;
            if (selection is Material)
            {
                Material mat = selection as Material;
                if(mat.HasProperty("_MainTex")) tex = mat.mainTexture;
            }
            else if (selection is Texture)
            {
                tex = selection as Texture;
            }
            if (tex != null)
            {
                //在新窗口打开图片大图
                if (GUILayout.Button(tex, GUILayout.Width(texWidth), GUILayout.Height(texHeight)))
                {
                    ZoomInTexture window = GetWindow<ZoomInTexture>();
                    window.texture = tex;
                    window.minSize = new Vector2(tex.width, tex.height);
                }
            }
            else
            {
                GUILayout.Box("N/A", GUILayout.Width(texWidth), GUILayout.Height(texHeight));
            }
        }
        else
        {
            GUILayout.Box("N/A", GUILayout.Width(texWidth), GUILayout.Height(texHeight));
        }
        GUILayout.Label("名称:"+selection.name + "\n类型:" + selection.GetType().ToString().Replace("UnityEngine.", "") + "\n路径:" + AssetDatabase.GetAssetPath(selection));
        GUILayout.EndHorizontal();
        GUILayout.Space(10);
    }

    //关联物体列表
    void ListMaterials()
    {
        if (dictMat == null || dictMat.Count <= 0)
        {
            GUILayout.Label("没有关联物体,请点击“开始查找关联”按钮");
            return;
        }
        foreach (string item in dictMat.Keys)
        {
            List<MatDetail> detailList = dictMat[item];
            if (detailList != null && detailList.Count > 0)
            {
                GUILayout.Space(20);
                GUILayout.Label("==================《" + item + "》【Material】==================");
                foreach (MatDetail detail in detailList)
                {
                    GUILayout.BeginHorizontal();
                    //输出可以点击寻找路径的图片
                    if (detail.mat != null && detail.mat.HasProperty("_MainTex") && detail.mat.mainTexture != null)
                    {
                        if (GUILayout.Button(detail.mat.mainTexture, GUILayout.Width(texWidth), GUILayout.Height(texHeight)))
                        {
                            Selection.activeObject = detail.mat;
                        }
                    }
                    else
                    {
                        if (GUILayout.Button("N/A", GUILayout.Width(texWidth), GUILayout.Height(texHeight)))
                        {
                            Selection.activeObject = detail.mat;
                        }
                    }
                    //输出信息
                    string print = "";
                    if(detail.type == "Effect" || detail.type == "Character")
                        print = string.Format("所在路径:{0}\n关联类型:{1}\n关联物体:{2}", detail.assetPath, detail.type, detail.hierarcyPath);
                    else
                        print = string.Format("所在路径:{0}\n关联场景:{1}\n关联物体:{2}", detail.assetPath, detail.type, detail.hierarcyPath);
                    if (detail.type == "NULL")
                    {
                        GUIStyle style = new GUIStyle();
                        style.normal.textColor = Color.red;
                        GUILayout.Label(print, style);
                        if (GUILayout.Button("删除", GUILayout.Width(40)))
                        {
                            if (AssetDatabase.DeleteAsset(detail.assetPath))
                            {
                                ShowNotification(new GUIContent("删除成功"));
                            }
                        }
                    }
                    else
                    {
                        GUILayout.Label(print);
                    }
                    GUILayout.EndHorizontal();
                }
            }
        }
    }

    //开始查找引用
    void OnCheckReferences()
    {
        Object[] objs = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
        if (objs == null || objs.Length <= 0) return;

        dictMat.Clear();
        for (int i = 0; i < objs.Length; i++)
        {
            List<MatDetail> listDetail = new List<MatDetail>();
            if (objs[i] is Material)
            {
                //遍历所有场景关联物体
                if (isCheckScene)
                {
                    if (EditorBuildSettings.scenes.Length <= 0)
                    {
                        Debug.LogError("你的可查找场景为空,请在Builder Setting中添加场景");
                        return;
                    }
                    foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
                    {
                        if (scene.enabled)
                        {
                            EditorApplication.OpenScene(scene.path);
                            if (objs[i] is Material)
                            {
                                MatDetail detail = SetMaterial(objs[i], scene.path);
                                if (detail != null) listDetail.Add(detail);
                            }
                        }
                    }
                }
                //遍历Resources特效目录
                if (isCheckEffect)
                {
                    Object[] effects = Resources.LoadAll("Effects");  //Resources下的地址,根据需求修改
                    for (int j = 0; j < effects.Length; j++)
                    {
                        if (objs[i] is Material)
                        {
                            MatDetail detail = SetEffectMaterial(objs[i], effects[j]);
                            DestroyImmediate(effectObj);
                            if (detail != null) listDetail.Add(detail);
                        }
                    }
                }
                //遍历角色目录
                if (isCheckCharacter)
                {
                    Object[] characters = GetAssetsOfType(Application.dataPath + "/Characters/", typeof(GameObject), ".prefab"); //自定义地址,根据需求修改
                    for (int j = 0; j < characters.Length; j++)
                    {
                        if (objs[i] is Material)
                        {
                            MatDetail detail = SetCharactersMaterial(objs[i], characters[j]);
                            DestroyImmediate(characterObj);
                            if (detail != null) listDetail.Add(detail);
                        }
                    }
                }
            }
            //找不到关联的处理
            if (listDetail.Count <= 0)
            {
                MatDetail detail = new MatDetail();
                detail.mat = objs[i] as Material;
                detail.assetPath = AssetDatabase.GetAssetPath(objs[i]);
                listDetail.Add(detail);
            }
            dictMat.Add(objs[i].name, listDetail);
        }
    }

    //获取prefab
    public static Object[] GetAssetsOfType(string directPath, System.Type type, string fileExtension)
    {
        List<Object> tempObjects = new List<Object>();
        DirectoryInfo directory = new DirectoryInfo(directPath);
        FileInfo[] goFileInfo = directory.GetFiles("*" + fileExtension, SearchOption.AllDirectories);

        int goFileInfoLength = goFileInfo.Length;
        FileInfo tempGoFileInfo;
        string tempFilePath;
        Object tempGO;
        for (int i = 0; i < goFileInfoLength; i++)
        {
            tempGoFileInfo = goFileInfo[i];
            if (tempGoFileInfo == null)
                continue;

            tempFilePath = tempGoFileInfo.FullName;
            tempFilePath = tempFilePath.Replace(@"\", "/").Replace(Application.dataPath, "Assets");

            tempGO = AssetDatabase.LoadAssetAtPath(tempFilePath, typeof(Object)) as Object;
            if (tempGO == null)
            {
                Debug.LogWarning("Skipping Null");
                continue;
            }
            else if (tempGO.GetType() != type)
            {
                Debug.LogWarning("Skipping " + tempGO.GetType().ToString());
                continue;
            }

            tempObjects.Add(tempGO);
        }

        return tempObjects.ToArray();
    }

    //获取场景材质
    MatDetail SetMaterial(Object obj, string scenePath)
    {
        Renderer[] renderers = (Renderer[])FindObjectsOfType(typeof(Renderer)); //这种取法方便,但是disactive的物体取不到,待完善
        return GetMatDetail(renderers, obj, scenePath);
    }

    //获取特效材质
    MatDetail SetEffectMaterial(Object select, Object effect)
    {
        effectObj = PrefabUtility.InstantiatePrefab(effect) as GameObject;
        if (effectObj == null) return null;
        Renderer[] renderers = effectObj.GetComponentsInChildren<Renderer>(true);
        return GetMatDetail(renderers, select, "Effect");
    }

    //获取角色材质
    MatDetail SetCharactersMaterial(Object select, Object character)
    {
        characterObj = PrefabUtility.InstantiatePrefab(character) as GameObject;
        if (characterObj == null) return null;
        SkinnedMeshRenderer[] renderers = characterObj.GetComponentsInChildren<SkinnedMeshRenderer>(true);
        foreach (SkinnedMeshRenderer renderer in renderers)
        {
            Material[] mats = renderer.sharedMaterials;
            foreach (Material mat in mats)
            {
                string assetPath = AssetDatabase.GetAssetPath(select);
                if (assetPath == AssetDatabase.GetAssetPath(mat))
                {
                    MatDetail detail = new MatDetail();
                    detail.assetPath = assetPath;
                    detail.mat = mat;
                    detail.type = "Character";
                    detail.hierarcyPath = GetHierarcyPath(renderer.gameObject);
                    return detail;
                }
            }
        }
        return null;
    }

    //设置并获取MatDetail
    MatDetail GetMatDetail(Renderer[] renderers, Object select, string scenePath)
    {
        foreach (Renderer renderer in renderers)
        {
            //获取材质
            Material[] mats = renderer.sharedMaterials;
            foreach (Material mat in mats)
            {
                string assetPath = AssetDatabase.GetAssetPath(select);
                if (assetPath == AssetDatabase.GetAssetPath(mat))
                {
                    MatDetail detail = new MatDetail();
                    detail.assetPath = assetPath;
                    detail.mat = mat;
                    detail.type = scenePath;
                    detail.hierarcyPath = GetHierarcyPath(renderer.gameObject);
                    return detail;
                }
            }
        }
        return null;
    }

    //设置路径
    string GetHierarcyPath(GameObject go)
    {
        string path = "/" + go.name;
        while (go.transform.parent != null)
        {
            go = go.transform.parent.gameObject;
            path = "/" + go.name + path;
        }
        return path;
    }

    //材质详情
    class MatDetail
    {
        public Material mat;
        public string assetPath;
        public string type;
        public string hierarcyPath;
        public MatDetail()
        {
            mat = null;
            assetPath = "";
            type = "NULL";
            hierarcyPath = "NULL";
        }
    }
}

//查看大图片
public class ZoomInTexture : EditorWindow
{
    public Texture texture;

    void OnGUI()
    {
        GUILayout.Box(texture, GUILayout.Width(texture.width), GUILayout.Height(texture.height));
    }
}
       这个工具还不是很完善,需要拓展和优化的地方还很多,不过还是足以满足当前需求的。当然最主要的是可以举一反三,抛砖引玉,通过此思路进一步去学习和完善工具插件,开源和分享才能取得最大的进步。最后,奉上工具截图:





Ricky Yang个人原创,版权所有,转载注明,谢谢。http://blog.csdn.net/yangyy753

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_yyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值