项目开发笔记 (九)
今天第二款业余独立游戏终于成功在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 Dictionarym_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-