编写目的:学习unity的sprite packer打图集后,发现unity没有提供一个工具去进行查找指定路径资源的双向引用(哪些资源引用的自己以及自己引用了哪些资源),也没有工具去替换指定资源的引用,这样当删除资源或者在工程里面移动资源(注意:在unity里面移动资源时,unity自动处理资源的引用关联),资源的引用就会丢失,从而要花费大量的人力去重新建立引用关联,而且还需要专人(美术或者开发)来维护资源的目录层级,这样对资源管理要求就比较高,一不留意就会存在资源多份存在造成包体过大以及资源位置放置不合理造成渲染批次增加,性能降低。鉴于以上在项目中遇到的坑以及痛苦的经历,所以决定写一个基于ugui的资源依赖关系查询工具,可以针对unity中任何资源(贴图,脚本,着色器,材质,模型,动画,字体等)进行操作。
查找依赖实现思路:
上面窗口是查找指定资源路径依赖了哪些资源对象,我们可以通过unity提供的AssetDatabase.GetDependencies接口来获取到列表,实现代码如下:
public static List<string> MeDependList(string file_path) {
List<string> depend_list = new List<string> ();
if (!File.Exists(file_path)) {
EditorUtility.DisplayDialog ("提示", "查找的资源不存在", "确定");
} else {
List<string> extensions = new List<string>() {".prefab", ".unity", ".mat", ".asset"};
string[] files = AssetDatabase.GetDependencies(new string[] {file_path});
foreach (string file in files) {
// 将合法的资源压入列表
if (extensions.Contains (Path.GetExtension (file).ToLower ())) {
depend_list.Add(file);
}
}
}
return depend_list;
}
上面窗口是查找指定资源路径被哪些资源对象引用了,要实现这个功能就必须对unity的本地ID和引用ID要有所了解,本地ID是资源对象在当前对象中的索引位置,用来区分一个对象中的多个不同资源,而引用ID就是资源在工程中的索引位置,unity会维护一张”引用ID vs 文件路径”的关联表,添加资源时会自动根据路径去查找guid,没有就分配一个新的guid并建立起关联。使用资源时会自动根据guid来获取关联的路径,所以我们可以通过查找文件中guid的方式来查看哪些文件包含了当前查找文件对应的guid,实现代码如下:
public static List<string> DependMeList(string file_path) {
List<string> depend_list = new List<string> ();
if (!File.Exists(file_path)) {
EditorUtility.DisplayDialog ("提示", "查找的资源不存在", "确定");
} else {
List<string> extensions = new List<string>() {".prefab", ".unity", ".mat", ".asset"};
// 获取自己的guid
string guid = AssetDatabase.AssetPathToGUID(file_path);
#if UNITY_EDITOR_OSX
// 获取查找自己guid的执行命令
var psi = new System.Diagnostics.ProcessStartInfo();
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Maximized;
psi.FileName = "/usr/bin/mdfind";
psi.Arguments = "-onlyin " + Application.dataPath + " " + guid;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
// 执行查找命令,获取资源列表
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = psi;
process.OutputDataReceived += (sender, e) => {
if(string.IsNullOrEmpty(e.Data)) {
return;
}
if (extensions.Contains (Path.GetExtension (e.Data).ToLower ())) {
string relative_path = EditorUtil.GetRelativeAssetsPath(e.Data);
depend_list.Add(relative_path);
}
}
process.ErrorDataReceived += (sender, e) => {
if(string.IsNullOrEmpty(e.Data)) {
return;
}
Debug.Log("Error: " + e.Data);
}
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(2000);
#else
// 获取资源列表
string[] files = Directory.GetFiles(Application.dataPath, "*.*", SearchOption.AllDirectories).Where(s => extensions.Contains(Path.GetExtension(s).ToLower())).ToArray();
// 获取匹配成功的资源列表
int start_index = 0;
EditorApplication.update = delegate() {
string file = files[start_index];
bool is_cancel = EditorUtility.DisplayCancelableProgressBar("匹配资源中...", file, (float)start_index / (float)files.Length);
if (Regex.IsMatch(File.ReadAllText(file), guid)) {
string relative_path = EditorUtil.GetRelativeAssetsPath(file);
depend_list.Add(relative_path);
}
start_index++;
if (is_cancel || start_index >= files.Length) {
EditorUtility.ClearProgressBar();
EditorApplication.update = null;
start_index = 0;
Debug.Log("匹配结束");
}
};
#endif
}
return depend_list;
}
在windows平台下通过查找整个工程目录将符合的格式文件保留并通过正则表达式匹配查找文件的guid的方式来获取依赖列表;这里查找速度相对较慢,资源越多对应的查找速度会越慢,但是还可以接受,主要是慢在正则匹配那里,相对而言mac平台的实现方式就相比windows实现方式要快的多,主要通过mdfind工具对查找文件的guid进行匹配查找,相比正则表达式查找方式要快很多,所以建议在mac环境下做查找依赖自己列表的操作。
替换依赖实现思路:
上面窗口是对查找指定资源引用的指定对象列表进行资源的替换操作,实现思路跟实现查找指定资源被依赖的资源列表思路一样,就是对当前依赖对象中的查找文件的guid进行替换成指定的资源文件guid就行了,目前测试了贴图,脚本,着色器,材质,模型,字体,音乐等资源文件的引用,均通过测试,实现代码如下:
public static bool ReplaceDepend(string search_file_path, List<string> src_file_list, string dest_file_path) {
if (!File.Exists(search_file_path)) {
EditorUtility.DisplayDialog("提示", "查找的资源不存在", "确定");
return false;
}
if (src_file_list.Count == 0) {
EditorUtility.DisplayDialog("提示", "替换的源资源不存在", "确定");
return false;
}
if (!File.Exists(dest_file_path)) {
EditorUtility.DisplayDialog("提示", "替换的目标资源不存在", "确定");
return false;
}
string search_ext = Path.GetExtension (search_file_path).ToLower ();
string dest_ext = Path.GetExtension (dest_file_path).ToLower ();
if (!search_ext.Equals (dest_ext)) {
EditorUtility.DisplayDialog("提示", string.Format("资源格式类型非法:[search:{0}, dest:{1}]", search_ext, dest_ext), "确定");
return false;
}
string search_guid = AssetDatabase.AssetPathToGUID(search_file_path);
string dest_guid = AssetDatabase.AssetPathToGUID(dest_file_path);
if (search_guid.Equals(dest_guid)) {
EditorUtility.DisplayDialog("提示", "查找资源和替换目标资源一致", "确定");
return false;
}
// 取消当前对象的选择状态
Selection.activeObject = null;
// 替换查找资源并刷新
foreach (string src_file_path in src_file_list) {
var content = File.ReadAllText(src_file_path);
content = content.Replace(search_guid, dest_guid);
File.WriteAllText(src_file_path, content);
}
AssetDatabase.Refresh();
return true;
}
总结:以上就是实现资源依赖关系查询和替换的核心思路和代码,现在唯一美中不足的就是替换窗口应该做成模态的,这样每次拖拽资源时不至于要聚焦替换窗口等不方便的操作,欢迎大家使用并给出好的修改建议。
参考地址:
http://www.xuanyusong.com/archives/4207
http://blog.csdn.net/u010019717/article/details/52763318
http://blog.csdn.net/su9257/article/details/53690411