Unity中根据资源搜索其在prefab中的引用

项目开发笔记 (九)

今天第二款业余独立游戏终于成功在GooglePlay上发布了,共计花了约1个月多的业余时间(每天下班后)。代码行数刚好1W出头。第一次以付费下载的模式上线,不管能不能卖出去但整个过程都非常有趣 每天都非常高效,思绪缠绕。对比公司的项目,真是效率低下。查看我最近三月的日志 关于公司项目的每天就2-3条信息,真是忍无可忍啊。废话说到这里,最近写了一个Unity中根据资源反查在prefab中引用的工具,今天我扯扯其中的原理。

prefab

预设,类似各种UI编辑器编辑后的输出文件,Unity中几乎任何事物都可以打包成预设,然后通过外部文件的形式再加载进程序里。不过是PNG/JPG等图片图集资源; GameObject Chartater之类的对象资源。

Unity中提供的搜索选项

Unity本身提供了一个资源引用搜索的选项,不过是针对当前Scene进行逐个资源搜索的,使用如图

这个选项并不能满足我们的需求,除了搜索某个资源在那个prefab被引用之外,在项目后期 我们还可能需要删除尚未被引用过的资源。

引用关系- References

一般的预设 并不是直接把PNG,JPG 合并成一个文件,而是添加它们的引用,预设与其说是UI界面等的导出文件生成文件,不如直接说是UI界面等的描述文件,他描述了某个界面引用了那个图片 那个按钮默认状态是什么 按钮显示Title是什么。举个例子,我们新建一个Panel(UGUI),随便添加一张图片,然后打包成prefab,如图所示

这样生成的prefab是加密的,需要在Editor中设置一下:

这样的设置下生成的prefab可以直接使用二进制编辑器查看里面内容,我刚刚打的prefab里面的部分内容:

MonoBehaviour:
  m_ObjectHideFlags: 1
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 100100000}
  m_GameObject: {fileID: 127900}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
  m_Name: 
  m_EditorClassIdentifier: 
  m_Material: {fileID: 0}
  m_Color: {r: 1, g: 1, b: 1, a: .39199999}
  m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
  m_Type: 1
  m_PreserveAspect: 0
  m_FillCenter: 1
  m_FillMethod: 4
  m_FillAmount: 1
  m_FillClockwise: 1
  m_FillOrigin: 0
--- !u!114 &11427902
MonoBehaviour:
  m_ObjectHideFlags: 1
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 100100000}
  m_GameObject: {fileID: 127902}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3}
  m_Name: 
  m_EditorClassIdentifier: 
  m_Material: {fileID: 0}
  m_Color: {r: 1, g: 1, b: 1, a: 1}
  m_Sprite: {fileID: 21300000, guid: 45ec39c559321054bae118f9cf92d953, type: 3}
  m_Type: 0
  m_PreserveAspect: 0
  m_FillCenter: 1
  m_FillMethod: 4
  m_FillAmount: 1
  m_FillClockwise: 1
  m_FillOrigin: 0
--- !u!222 &22227900

从中可以看出

1.设计prefab变量命名方式的程序员真是和我一样属于强迫症重度患者。成员变量命名也一丝不苟
2.prefab中详细记录了每一项的信息
3.prefab中引用资源是使用guid的方式引用的(关键点)

查了一下,Unity为每个资源都分配了一个guid,然后根据guid来引用各个资源。获取某个资源的GUID的代码如下:

var path = AssetDatabase.GetAssetPath(o);
string strGUID = AssetDatabase.AssetPathToGUID(path);
// 由此得出,上面的图片(那个猪)GUID为:45ec39c559321054bae118f9cf92d953

在拿到图片的GUID字符串之后,到prefab里反查可以得到在 71 行的地方:

...
m_Sprite: {fileID: 21300000, guid: 45ec39c559321054bae118f9cf92d953, type: 3}
...

如此就可以判断图片被这个prefab引用过一次。使用搜索GUID的方式可以在任何prefab中搜索任何资源的引用次数。我实现了一个工具,里面包含了基本的功能:

1.搜索1个资源(纹理,图片,材质,特效等)在N个prefab中被引用的次数
2.搜索N个资源(纹理,图片,材质,特效等)在N个prefab中被引用的次数
3.找到N个资源(纹理,图片,材质,特效等)中尚未被引用过的资源

代码如下:

PluginMenu.cs

//*************************************************************************
//  创建日期:   2016-1-14
//  文件名称:   PluginMenu.cs
//  创建作者:   Rect    
//  版权所有:   Shadowkong.com
//  相关说明:   资源被预设引用反查工具
//*************************************************************************


//-------------------------------------------------------------------------
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

public static class PluginMenu
{

    #region Plugins Menu
    
    [MenuItem("Plugins/UI/资源引用分析", false, 3)]
    public static void OpenRes2Check()
    {
        ResourceCheckTool.Open();
    }

    #endregion
}

ResourceCheckTool.cs

//*************************************************************************
//  创建日期:   2016-1-14
//  文件名称:   ResourceCheckTool.cs
//  创建作者:   Rect    
//  版权所有:   Shadowkong.com
//  相关说明:   资源被预设引用反查工具
//*************************************************************************


//-------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.IO;
using System;
using Object = UnityEngine.Object;
using System.Text;

public class ResourceCheckTool : EditorWindow
{
    #region Member variables
    //-------------------------------------------------------------------------
    private List m_SelectPathList;
    private List m_SelectGUIDList;
    private List m_PrefabList;
    private List M_ResNotBeRefs;
    private Dictionary> m_DicResPrefabs;
    private string m_strUIPerfabPath;
    private string m_strCurrentPerfabPath;
    private Vector2 m_scrollPos;
    //-------------------------------------------------------------------------
    #endregion

    #region Public Method
    //-------------------------------------------------------------------------
    public static void Open()
    {

        GetWindowWithRect(new Rect(0, 0, 700, 800), true);

    }
    //-------------------------------------------------------------------------
    void OnGUI()
    {
        if (null != m_SelectPathList && 0 != m_SelectPathList.Count)
        {

            m_scrollPos = GUILayout.BeginScrollView(m_scrollPos, true, true, GUILayout.Height(800));
            GUILayout.Space(10);
            GUI.backgroundColor = Color.green;
            if (GUILayout.Button("重新分析 - UI", GUILayout.Height(50)))
            {
                __Init();
                __GetSelectItem();
                __GetAllPrefabs(m_strCurrentPerfabPath);
                __CheckEveryPrefab();
                m_strCurrentPerfabPath = m_strUIPerfabPath;
            }
            GUILayout.Space(10);

            List lists = null;
            GUILayout.Label("当前预设资源路径为:");
            GUILayout.Label(m_strCurrentPerfabPath);

            if (0 == m_SelectPathList.Count)
            {
                GUILayout.Label("-------------------------------------------------------------------------");
                GUILayout.Label("当前分析的资源为:");
                GUILayout.Label("此资源尚未被任何预设引用过,可以考虑删除!");
            }

            for (int nInedx = 0; nInedx != m_SelectPathList.Count; ++nInedx)
            {
                if (m_DicResPrefabs.TryGetValue(m_SelectGUIDList[nInedx], out lists))
                {
                    if (null != lists && 0 != lists.Count)
                    {
                        GUILayout.Label("-------------------------------------------------------------------------");
                        GUILayout.Label("当前分析的资源为:");
                        GUILayout.Label(m_SelectPathList[nInedx]);
                        GUILayout.Label("当前分析的资源引用信息为:");

                        foreach (string str in lists)
                        {
                            GUILayout.Label(str);
                        }
                    }
                    else
                    {
                        M_ResNotBeRefs.Add(m_SelectPathList[nInedx]);
                    }
                }

            }

            if (0 != M_ResNotBeRefs.Count)
            {
                GUILayout.Label("-------------------------------------------------------------------------");
                GUILayout.Label("以下为完全没被引用过的资源:");
                foreach (string str in M_ResNotBeRefs)
                {
                    GUILayout.Label(str);
                }
                M_ResNotBeRefs.Clear();
            }

            GUILayout.Label("-------------------------------------------------------------------------");

            GUILayout.EndScrollView();


        }
        else
        {
            GUILayout.Space(10);
            GUILayout.Label("当前没有资源被选中,选择你需要分析的资源然后点【分析】");
            GUILayout.Space(100);

            GUI.backgroundColor = Color.red;
            if (GUILayout.Button("分析 - UI", GUILayout.Height(50)))
            {
                __Init();
                __GetSelectItem();
                __GetAllPrefabs(m_strUIPerfabPath);
                __CheckEveryPrefab();
                m_strCurrentPerfabPath = m_strUIPerfabPath;
            }
            
            GUILayout.Space(10);

        }

    }
    //-------------------------------------------------------------------------
    #endregion

    #region private Method
    //-------------------------------------------------------------------------
    private void __Init()
    {
        if (null == m_SelectPathList)
        {
            m_SelectPathList = new List();
        }

        m_SelectPathList.Clear();

        if (null == m_SelectGUIDList)
        {
            m_SelectGUIDList = new List();
        }

        m_SelectGUIDList.Clear();

        if (null == m_PrefabList)
        {
            m_PrefabList = new List();
        }
        m_PrefabList.Clear();

        if (null == m_DicResPrefabs)
        {
            m_DicResPrefabs = new Dictionary>();
        }
        m_DicResPrefabs.Clear();

        if (null == M_ResNotBeRefs)
        {
            M_ResNotBeRefs = new List();
        }
        M_ResNotBeRefs.Clear();

        m_strUIPerfabPath = "Assets/Resources/UI/";
    }

    //-------------------------------------------------------------------------
    private void __GetSelectItem()
    {
        foreach (Object o in Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets))
        {
            var path = AssetDatabase.GetAssetPath(o);

            // 过滤掉meta文件和文件夹
            if (path.Contains(".meta") || path.Contains(".") == false)
            {
                continue;
            }

            m_SelectPathList.Add(path);
            m_SelectGUIDList.Add(AssetDatabase.AssetPathToGUID(path));

            Debug.Log("path = " + path);
            Debug.Log("GUID = " + AssetDatabase.AssetPathToGUID(path));
        }
    }
    //-------------------------------------------------------------------------
    private void __GetAllPrefabs(string strPrefabsPath)
    {
        if (null == m_SelectPathList || 0 == m_SelectPathList.Count)
        {
            Debug.Log("请在Project中选择需要分析的资源!");
            return;
        }

        var dirArr = Directory.GetDirectories(strPrefabsPath);
        for (int i = 0; i < dirArr.Length; i++)
        {
            var pathArr = __GetFiles(dirArr[i]);
            for (int j = 0; j < pathArr.Length; j++)
            {
                var filePath = pathArr[j];
                var progress = i * 1f / dirArr.Length + (j + 1f) / pathArr.Length / dirArr.Length;
                // Debug.Log("filePath: "+ "[" + i + "]" + "[" + j + "] = " + filePath);
                m_PrefabList.Add(filePath);
            }

        }

        var paths = __GetFiles(strPrefabsPath);
        for (int j = 0; j < paths.Length; j++)
        {
            var filePath = paths[j];
            // Debug.Log("filePath: " +  "[" + j + "] = " + filePath);
            m_PrefabList.Add(filePath);
        }

        Debug.Log(" m_PrefabList.Count = " + m_PrefabList.Count);
    }
    //-------------------------------------------------------------------------
    private void __CheckEveryPrefab()
    {
        if (null == m_SelectGUIDList || 0 == m_SelectGUIDList.Count)
        {
            return;
        }

        if (null == m_SelectPathList || 0 == m_SelectPathList.Count)
        {
            return;
        }

        if (null == m_PrefabList || 0 == m_PrefabList.Count)
        {
            return;
        }

        m_DicResPrefabs.Clear();
        int nLen = m_SelectPathList.Count;
        for (int nInedx = 0; nInedx != nLen; ++nInedx)
        {
            EditorUtility.DisplayProgressBar("搜索预设引用关系", "搜索中..." + m_SelectPathList[nInedx], nInedx / nLen);
            string strFilePath = m_SelectPathList[nInedx];
            string strFileGUID = m_SelectGUIDList[nInedx];
            List list = null;
            if (!m_DicResPrefabs.TryGetValue(strFileGUID, out list))
            {
                list = new List();
                list.Clear();
                m_DicResPrefabs.Add(strFileGUID, list);
            }

            if (null != list)
            {
                __CheckByGUID(strFileGUID, ref list);
            }

        }

        EditorUtility.ClearProgressBar();

    }
    //-------------------------------------------------------------------------
    private void __CheckByGUID(string strGUID, ref  List list)
    {
        if (null == strGUID || null == list)
        {
            return;
        }

        if (null == m_PrefabList || 0 == m_PrefabList.Count)
        {
            return;
        }

        int nLen = m_PrefabList.Count;
        string strPrefabFile = null;
        try
        {
            for (int nIndex = 0; nIndex != nLen; ++nIndex)
            {
                strPrefabFile = m_PrefabList[nIndex];
                FileStream fs = new FileStream(strPrefabFile, FileMode.Open, FileAccess.Read);
                byte[] buff = new byte[fs.Length];
                fs.Read(buff, 0, (int)fs.Length);
                string strText = Encoding.Default.GetString(buff);

                int nStar = 0;
                int nCount = 0;
                while (-1 != nStar)
                {
                    nStar = strText.IndexOf(strGUID, nStar);
                    if (-1 != nStar)
                    {
                        nCount++;
                        nStar++;
                    }
                }

                if (0 != nCount)
                {

                    strText = m_PrefabList[nIndex] + " 在此资源中被引用 " + nCount + " 次";
                    list.Add(strText);
                }


            }
        }
        catch (System.Exception ex)
        {
            Debug.Log("__CheckByGUID Error");
        }

    }
    //-------------------------------------------------------------------------
    /// 
    /// 获取目录下的所有对象路径,去掉了.meta
    /// 
    /// 目录路径
    /// 是否递归获取
    /// 
    private  string[] __GetFiles(string path, bool recursive = true)
    {
        var resultList = new List();
        var dirArr = Directory.GetFiles(path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
        for (int i = 0; i < dirArr.Length; i++)
        {
            if (Path.GetExtension(dirArr[i]) != ".meta")
                resultList.Add(dirArr[i].Replace('\\', '/'));
        }
        return resultList.ToArray();
    }
    //-------------------------------------------------------------------------
    #endregion

}

运行效果如下:

-EOF-

《Unity中根据资源搜索其在prefab中的引用》有7个想法

  1. 前几天这里留言不能- -提示数据库访问错误……
    话说你这第二款独立游戏叫什么,现状如何?

  2. mark一下,后期项目中肯定需要用到这样的工具,十分感谢博主的热心分享。
    顺便问一下,博主的博客是用什么搭建的?

  3. 大佬 为何不来个正确的版本 好多报错啊 萌新表示不会改啊

    1. 这个原理很简单:
      0.获取资源的udid
      1.根据udid在预制体文件中字符串搜索匹配
      尝试自己实现1下?

  4. 确实基本上每一句都报错,因为这个网页估计 吞了不少尖括号还有尖括号内的文字 因为List和字典声明很诡异

Rect进行回复 取消回复

邮箱地址不会被公开。 必填项已用*标注