精确的Prefab资源引用查找工具
2026/01
06
16:01
一般的Prefab引用查找工具,只能查找Prefb引用了,但是哪个节点引用了不知道,以下工具可以精确定位是哪个节点使用的
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Text;
using UnityEditor.SceneManagement;
using UnityEditor.Animations;
public class PrefabRefFindTool: EditorWindow
{
[MenuItem("Tools/打开Prefab资源查找工具", false, 3502)]
private static void ShowWindow()
{
var window = GetWindow<PrefabRefFindTool>("Prefab资源查找工具");
window.Show();
}
public static bool UseDebugPath = false;
public class FilterObject
{
private List<string> mExcludeFilter = new List<string>();
private List<string> mIncludeFilter = new List<string>();
private List<string> mFileTypeFilter = new List<string>();
public FilterObject(string excludeStr, string includeStr, string[] fileTypeFilter)
{
// 过滤掉Unity内置资源
mExcludeFilter.Add("unity_builtin_extra");
mExcludeFilter.Add("library/");
mExcludeFilter.Add("packages/");
var exclude = excludeStr.ToLower().Split(';');
foreach (var item in exclude)
{
var txt = item.Trim();
if (txt.Length > 0)
{
mExcludeFilter.Add(txt);
}
}
var include = includeStr.ToLower().Split(';');
foreach (var item in include)
{
var txt = item.Trim();
if (txt.Length > 0)
{
mIncludeFilter.Add(txt);
}
}
mFileTypeFilter.AddRange(fileTypeFilter);
}
public bool IsShow(string input0)
{
var input = input0.ToLower();
if (mExcludeFilter.Count > 0)
{
foreach (var str in mExcludeFilter)
{
if (input.Contains(str))
{
return false;
}
}
}
if (mFileTypeFilter.Count > 0)
{
bool container = false;
foreach (var str in mFileTypeFilter)
{
if (input.Contains(str))
{
container = true;
break;
}
}
if (!container)
{
return false;
}
}
if (mIncludeFilter.Count > 0)
{
bool container = false;
foreach (var str in mIncludeFilter)
{
if (input.Contains(str))
{
container = true;
break;
}
}
if (!container)
{
return false;
}
}
return true;
}
}
public class PrefabRefInfos
{
// prefabPath : List<nodePath>
public Dictionary<string, List<(string path, string etype)>> mPrefabRefInfos = new ();
public bool mFoldout = false;
public bool mSelected = false;
private string mETypes = "";
public string ETypes => mETypes;
public void AddRefInfo(string prefabPath, string nodePath, string eType)
{
if (!mPrefabRefInfos.TryGetValue(prefabPath, out var nodePaths))
{
nodePaths = new List<(string path, string etype)>();
mPrefabRefInfos.Add(prefabPath, nodePaths);
}
bool contain = false;
foreach(var (path, etype) in nodePaths)
{
if (path == nodePath)
{
contain = true;
break;
}
}
if (!mETypes.Contains(eType))
{
mETypes += $"{eType};";
}
if (!contain)
{
nodePaths.Add((nodePath, eType));
}
}
public (string, string) Draw( (string p3, string p4) prev)
{
var (p1, p2) = (string.Empty, string.Empty);
EditorGUILayout.BeginVertical("box");
foreach (var kv in mPrefabRefInfos)
{
var prefabPath = kv.Key;
var nodePaths = kv.Value;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(prefabPath);
if (GUILayout.Button("全选节点", GUILayout.Width(70)))
{
List<string> list = new List<string>();
foreach(var (nodePath, eType) in nodePaths)
{
list.Add(nodePath);
}
SelectAllNode(prefabPath, list);
}
DrawLocationButton(prefabPath);
EditorGUILayout.EndHorizontal();
foreach (var (nodePath, eType) in nodePaths)
{
EditorGUILayout.BeginHorizontal();
var title = ">";
if (prev.p3 == prefabPath && prev.p4 == nodePath)
{
title = "* >";
}
if (GUILayout.Button(title, GUILayout.Width(30)))
{
var ret = SelectNodeByPath(prefabPath, nodePath);
if (ret != 0)
{
Log.EditorInfo($"选中{nodePath}节点失败,错误码:{ret}");
}
else
{
p1 = prefabPath;
p2 = nodePath;
}
}
EditorGUILayout.LabelField(nodePath);
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndVertical();
return (p1, p2);
}
}
private static void SelectAllNode(string prefabPath, List<string> nodePaths)
{
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null)
{
if (prefabStage.assetPath != prefabPath)
{
PrefabStageUtility.OpenPrefab(prefabPath);
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
}
}
else
{
PrefabStageUtility.OpenPrefab(prefabPath);
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
}
var rootNode = prefabStage.prefabContentsRoot;
if (rootNode == null)
{
return;
}
List<GameObject> collect = new List<GameObject>();
for(int i=0; i<nodePaths.Count; ++i)
{
var node = FindNodeByPath(rootNode, nodePaths[i]);
if (node != null)
{
collect.Add(node.gameObject);
}
}
if (collect.Count > 0)
{
Selection.objects = collect.ToArray();
}
}
private static GameObject FindNodeByPath(GameObject parentNode, string nodePath)
{
var nodePaths = nodePath.Split('/');
if (nodePaths.Length == 0)
{
return null;
}
if (nodePaths[0] != parentNode.name)
{
return null;
}
var node = parentNode.transform;
for(int i=1; i<nodePaths.Length; i++)
{
node = node.transform.Find(nodePaths[i]);
if (node == null)
{
break;
}
}
if (node != null)
{
return node.gameObject;
}
return null;
}
private static int SelectNodeByPath(string prefabPath, string nodePath)
{
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null)
{
if (prefabStage.assetPath != prefabPath)
{
PrefabStageUtility.OpenPrefab(prefabPath);
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
}
}
else
{
PrefabStageUtility.OpenPrefab(prefabPath);
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
}
var nodePaths = nodePath.Split('/');
if (nodePaths.Length == 0)
{
return -3;
}
var rootNode = prefabStage.prefabContentsRoot;
if (rootNode == null)
{
return -4;
}
if (nodePaths[0] != rootNode.name)
{
return -5;
}
var node = rootNode.transform;
for(int i=1; i<nodePaths.Length; i++)
{
node = node.transform.Find(nodePaths[i]);
if (node == null)
{
break;
}
}
if (node == null)
{
return -6;
}
Selection.activeTransform = node;
return 0;
}
private static void DrawLocationButton(string prefabPath)
{
if (GUILayout.Button("定位", GUILayout.Width(40)))
{
var selectObj = AssetDatabase.LoadAssetAtPath<Object>(prefabPath);
if (selectObj != null)
{
Selection.activeObject = selectObj;
}
}
}
private Vector2 mMainScrollPos = Vector2.zero;
private DefaultAsset mTargetDir;
private Dictionary<string, PrefabRefInfos> mTexturePathDict = new ();
private string mExcludeStr = "";
private string mIncludeStr = "";
private string mFilterStr = "";
private readonly string[] kShowFilters = new string[]
{
"全部", // 0
"仅图片", // 1
"仅FBX", // 2
"仅材质", // 3
};
private readonly string[][] kShowFilterArray = new string[][]
{
new string[] {},
new string[] {".png", ".jpg", ".tga"},
new string[] {".fbx"},
new string[] {".mat"},
};
private int mShowType = 0;
private void SetAllSelectState(bool selected)
{
foreach (var kv in mTexturePathDict)
{
kv.Value.mSelected = !selected;
}
var filter = GetFilterObj();
List<string> keys = new List<string>(mTexturePathDict.Keys);
keys.Sort();
foreach(var key in keys)
{
if (filter.IsShow(key))
{
mTexturePathDict[key].mSelected = selected;
}
}
}
private bool mUseUnityDepSys = false;
private void OnGUI()
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("预览选中", GUILayout.Width(80)))
{
PreviewImages();
}
if (GUILayout.Button("全选", GUILayout.Width(70)))
{
SetAllSelectState(true);
}
if (GUILayout.Button("全不选", GUILayout.Width(70)))
{
SetAllSelectState(false);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
var newShowType = EditorGUILayout.Popup(mShowType, kShowFilters, GUILayout.Width(80));
if (newShowType != mShowType)
{
mShowType = newShowType;
}
EditorGUILayout.LabelField("排除", GUILayout.Width(30));
mExcludeStr = EditorGUILayout.TextField(mExcludeStr, GUILayout.Width(100));
EditorGUILayout.LabelField(" ", GUILayout.Width(5));
EditorGUILayout.LabelField("包含", GUILayout.Width(30));
mIncludeStr = EditorGUILayout.TextField(mIncludeStr, GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("移动纹理到", GUILayout.Width(80));
mTargetDir = (DefaultAsset)EditorGUILayout.ObjectField("", mTargetDir, typeof(DefaultAsset), false, GUILayout.Width(160));
if (mTargetDir != null && GUILayout.Button("移动", GUILayout.Width(80)))
{
MoveSelectImages();
}
EditorGUILayout.LabelField(" ", GUILayout.Width(5));
mUseUnityDepSys = EditorGUILayout.Toggle(mUseUnityDepSys, GUILayout.Width(20));
EditorGUILayout.LabelField("使用Unity依赖", GUILayout.Width(80));
UseDebugPath = EditorGUILayout.Toggle(UseDebugPath, GUILayout.Width(20));
EditorGUILayout.LabelField("引用过滤", GUILayout.Width(50));
if (UseDebugPath)
{
mFilterStr = EditorGUILayout.TextField(mFilterStr, GUILayout.Width(80));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginVertical();
mMainScrollPos = EditorGUILayout.BeginScrollView(mMainScrollPos);
List<string> keys = new List<string>(mTexturePathDict.Keys);
keys.Sort();
var filter = GetFilterObj();
foreach(var key in keys)
{
if (!filter.IsShow(key))
{
continue;
}
var currTexInfo = mTexturePathDict[key];
if (UseDebugPath && mFilterStr.Length > 0)
{
if (!currTexInfo.ETypes.Contains(mFilterStr))
{
continue;
}
}
EditorGUILayout.BeginHorizontal();
currTexInfo.mSelected = EditorGUILayout.Toggle(currTexInfo.mSelected, GUILayout.Width(20));
currTexInfo.mFoldout = EditorGUILayout.Foldout(currTexInfo.mFoldout, key);
DrawLocationButton(key);
EditorGUILayout.EndHorizontal();
if (currTexInfo.mFoldout)
{
(var p0, var p1) = currTexInfo.Draw(mLastSelectInfo);
if(p0.Length > 0 && p1.Length > 0)
{
mLastSelectInfo = (p0, p1);
}
}
else
{
if (UseDebugPath)
{
EditorGUILayout.LabelField(currTexInfo.ETypes);
}
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
if (keys.Count == 0)
{
EditorGUILayout.HelpBox("排除和包含,以;(分号)分隔", MessageType.Info);
}
}
private (string lastP0, string lastP1) mLastSelectInfo = (string.Empty, string.Empty);
private FilterObject GetFilterObj()
{
var filterArray = kShowFilterArray[mShowType];
return new FilterObject(mExcludeStr, mIncludeStr, filterArray);
}
private void MoveSelectImages()
{
var targetDirPath = AssetDatabase.GetAssetPath(mTargetDir);
if (!targetDirPath.StartsWith("Assets/"))
{
Log.EditorError("目标目录必须在Assets目录下");
return;
}
StringBuilder sb = new StringBuilder();
foreach (var kv in mTexturePathDict)
{
if (!kv.Value.mSelected)
{
continue;
}
var ret = MoveImageAsset(kv.Key, targetDirPath);
sb.AppendLine(ret);
}
Log.EditorInfo(sb.ToString());
}
private string MoveImageAsset(string srcImagePath, string targetDirPath)
{
if (!System.IO.File.Exists(srcImagePath))
{
return $"{srcImagePath}文件不存在";
}
var fileName = System.IO.Path.GetFileName(srcImagePath);
var destImagePath = System.IO.Path.Combine(targetDirPath, fileName);
if (System.IO.File.Exists(destImagePath))
{
return $"{destImagePath}文件已存在";
}
if (srcImagePath == destImagePath)
{
return $"{srcImagePath}和{destImagePath}相同";
}
AssetDatabase.MoveAsset(srcImagePath, destImagePath);
return $"移动成功: {srcImagePath} -> {destImagePath}";
}
private void PreviewImages()
{
mTexturePathDict.Clear();
StringBuilder sb = new StringBuilder();
var (type, dir) = EditorHelper.GetCurrSelectAssetPath();
if ( type == "dir")
{
var files = System.IO.Directory.GetFiles(dir, "*.prefab", System.IO.SearchOption.AllDirectories);
foreach (var assetPath in files)
{
sb.Append(assetPath.Replace("\\", "/"));
sb.Append(";");
}
}
else
{
var selectedObjs = Selection.objects;
if (selectedObjs.Length == 0)
{
Log.EditorError($"请在Project中, 选择要预览的Prefab,可以多选");
return;
}
foreach (var obj in selectedObjs)
{
var assetPath = AssetDatabase.GetAssetPath(obj);
if (!string.IsNullOrEmpty(assetPath))
{
sb.Append(assetPath);
sb.Append(";");
}
}
}
var str = sb.ToString();
PreviewImages(str);
}
private void PreviewImages(string prefabAssetPaths)
{
CollectAndPreviewImages(mTexturePathDict, prefabAssetPaths, mUseUnityDepSys);
}
private static void CollectMatTextures(Material material, List<string> texturePaths)
{
var shader = material.shader;
if (shader == null)
{
return;
}
var propCount = shader.GetPropertyCount();
for (int i = 0; i < propCount; i++)
{
if (shader.GetPropertyType(i) != UnityEngine.Rendering.ShaderPropertyType.Texture)
{
continue;
}
var propName = shader.GetPropertyName(i);
if (string.IsNullOrEmpty(propName))
{
continue;
}
var texture = material.GetTexture(propName);
if (texture == null)
{
continue;
}
var texPath = AssetDatabase.GetAssetPath(texture);
if (string.IsNullOrEmpty(texPath))
{
continue;
}
if (texturePaths.Contains(texPath))
{
continue;
}
texturePaths.Add(texPath);
}
}
public delegate void CollectFunc(string prefabAssetPath, Dictionary<string, PrefabRefInfos> pool);
// 收集所有的引用
private static void CollectSelectPrefabAssets(string prefabAssetPath, Dictionary<string, PrefabRefInfos> pool)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabAssetPath);
if (prefab == null)
{
return;
}
// 追加引用
void AppendAssetRef(GameObject node, string newAssetPath, string reason)
{
if (node == null || string.IsNullOrEmpty(newAssetPath))
{
return;
}
if (!pool.TryGetValue(newAssetPath, out var info))
{
info = new PrefabRefInfos();
pool.Add(newAssetPath, info);
}
var fullPathName = node.GetFullPathName();
info.AddRefInfo(prefabAssetPath, fullPathName, reason);
}
// 收集GameObject引用的贴图
void AppendObjRef(GameObject node, UnityEngine.Object refObj, string reason)
{
if (refObj == null)
{
return;
}
AppendAssetRef(node, AssetDatabase.GetAssetPath(refObj), reason);
if (refObj is Material)
{
// 如果是材质,还需要收集材质中引用的贴图
var materialObj = refObj as Material;
List<string> texturePaths = new List<string>();
CollectMatTextures(materialObj, texturePaths);
foreach (var texturePath in texturePaths)
{
AppendAssetRef(node, texturePath, reason);
}
}
}
// 收集Image
var images = prefab.GetComponentsInChildren<UnityEngine.UI.Image>(true);
foreach (var image in images)
{
if (image.mainTexture != null)
{
AppendObjRef(image.gameObject, image.mainTexture, "MainTex-Image");
}
if (image.material != null)
{
AppendObjRef(image.gameObject, image.material, "Material-Image");
AppendObjRef(image.gameObject, image.material.mainTexture, "MainTex-ImageMaterial");
}
}
// 收集RawImage
var rawImages = prefab.GetComponentsInChildren<UnityEngine.UI.RawImage>(true);
foreach (var rawImage in rawImages)
{
if (rawImage.mainTexture != null)
{
AppendObjRef(rawImage.gameObject, rawImage.mainTexture, "MainTex-RawImage");
}
if (rawImage.material != null)
{
AppendObjRef(rawImage.gameObject, rawImage.material, "Material-RawImage");
AppendObjRef(rawImage.gameObject, rawImage.material.mainTexture, "Material-MainTex");
}
}
// 收集SpriteRenderer
var spriteRenderers = prefab.GetComponentsInChildren<SpriteRenderer>(true);
foreach (var spriteRenderer in spriteRenderers)
{
if (spriteRenderer.sprite && spriteRenderer.sprite.texture)
{
AppendObjRef(spriteRenderer.gameObject, spriteRenderer.sprite.texture, "MainTex-SpriteRenderer");
}
}
// 收集渲染器
var renderers = prefab.GetComponentsInChildren<Renderer>(true);
foreach (var renderer in renderers)
{
if (renderer.sharedMaterial != null)
{
AppendObjRef(renderer.gameObject, renderer.sharedMaterial, "Material-Renderer");
AppendObjRef(renderer.gameObject, renderer.sharedMaterial.mainTexture, "Material-MainTex");
}
}
// 收集MeshCollider中的FBX中
var meshColliders = prefab.GetComponentsInChildren<MeshCollider>(true);
foreach (var meshCollider in meshColliders)
{
if (meshCollider.sharedMesh != null)
{
AppendObjRef(meshCollider.gameObject, meshCollider.sharedMesh, "Mesh-MeshCollider");
}
}
// 收集Animation中的Clip
var animations = prefab.GetComponentsInChildren<Animation>(true);
foreach (var animation in animations)
{
if (animation.clip != null)
{
AppendObjRef(animation.gameObject, animation.clip, "AnimationClip");
}
}
// 收集粒子系统中的Mesh
void AppendSharpAsset(ParticleSystem particleSystem)
{
if (particleSystem.shape.mesh == null)
{
return;
}
AppendObjRef(particleSystem.gameObject, particleSystem.shape.mesh, "Mesh-Particle");
var fbxPath = AssetDatabase.GetAssetPath(particleSystem.shape.mesh);
if (string.IsNullOrEmpty(fbxPath))
{
return;
}
var fbxDepends = AssetDatabase.GetDependencies(fbxPath, true);
foreach (var dependAsset in fbxDepends)
{
var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(dependAsset);
Log.EditorInfo(dependAsset);
AppendObjRef(particleSystem.gameObject, obj, "ParticleDepends");
}
}
// 收集粒子系统
var particleSystems = prefab.GetComponentsInChildren<ParticleSystem>(true);
foreach (var particleSystem in particleSystems)
{
var psr = particleSystem.GetComponent<ParticleSystemRenderer>();
if (psr != null)
{
if (psr.sharedMaterial != null)
{
AppendObjRef(particleSystem.gameObject, psr.sharedMaterial, "Material-Particle");
try
{
AppendObjRef(particleSystem.gameObject, psr.sharedMaterial.mainTexture, "MainTex-Particle");
}
catch
{
}
}
if (psr.trailMaterial != null)
{
AppendObjRef(particleSystem.gameObject, psr.trailMaterial, "Material-Trail");
try
{
AppendObjRef(particleSystem.gameObject, psr.trailMaterial.mainTexture, "MainTex-Trail");
}
catch
{
}
}
// 收集ParticleSystemRenderMode中的Meshs
if (psr.renderMode == ParticleSystemRenderMode.Mesh)
{
Mesh[] meshes = new Mesh[psr.meshCount];
psr.GetMeshes(meshes);
for(int i=0; i<meshes.Length; ++i)
{
AppendObjRef(particleSystem.gameObject, meshes[i], "Mesh-Particle");
}
}
}
AppendObjRef(particleSystem.gameObject, particleSystem.shape.texture, "MainTex-Particle");
AppendSharpAsset(particleSystem);
}
// 收集Mesh
var meshs = prefab.GetComponentsInChildren<MeshFilter>(true);
foreach (var meshFilter in meshs)
{
AppendObjRef(meshFilter.gameObject, meshFilter.sharedMesh, "Mesh-MeshFilter");
}
// 收集VFXImage
var vfxImages = prefab.GetComponentsInChildren<VFXImage>(true);
foreach (var vfxImage in vfxImages)
{
AppendObjRef(vfxImage.gameObject, vfxImage.CustomMesh, "Mesh-VFXImage");
}
// 收集动画
var animators = prefab.GetComponentsInChildren<Animator>(true);
foreach (var animator in animators)
{
if (animator.runtimeAnimatorController == null)
{
continue;
}
var ac = animator.runtimeAnimatorController as AnimatorController;
if (ac == null)
{
continue;
}
foreach (var layer in ac.layers)
{
foreach (var state in layer.stateMachine.states)
{
if (state.state.motion == null)
{
continue;
}
// Anim动画
AppendObjRef(animator.gameObject, state.state.motion, "AnimMotion");
}
}
// 控制器文件
AppendObjRef(animator.gameObject, ac, "AnimLayerMotion");
}
}
private readonly static List<string> unityDepExcludeExts = new List<string>() { ".cs", ".dll", ".shader" };
private static void CollectSelectPrefabAllDepends(string prefabAssetPath, Dictionary<string, PrefabRefInfos> pool)
{
var depends = AssetDatabase.GetDependencies(prefabAssetPath);
foreach (var dependAsset in depends)
{
if (prefabAssetPath == dependAsset)
{
continue;
}
var ext = System.IO.Path.GetExtension(dependAsset).ToLower();
if (unityDepExcludeExts.Contains(ext))
{
continue;
}
if (!pool.TryGetValue(dependAsset, out var info))
{
info = new PrefabRefInfos();
pool.Add(dependAsset, info);
}
info.AddRefInfo(prefabAssetPath, dependAsset, "Unity");
}
}
public static void CollectAndPreviewImages(Dictionary<string, PrefabRefInfos> result, string prefabAssetPaths, bool useUnityDepends)
{
if (prefabAssetPaths.Length == 0)
{
return;
}
CollectFunc collectFunc = useUnityDepends ? CollectSelectPrefabAllDepends : CollectSelectPrefabAssets;
result.Clear();
var prefabAssetPathsList = prefabAssetPaths.Split(';');
foreach (var prefabAssetPath in prefabAssetPathsList)
{
collectFunc(prefabAssetPath, result);
}
}
}
CopyRights: The Post by BY-NC-SA For Authorization,Original If Not Noted,Reprint Please Indicate From 老刘@开发笔记
Post Link: 精确的Prefab资源引用查找工具
Post Link: 精确的Prefab资源引用查找工具