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