Unity 编辑器扩展:一键查找场景中组件引用关系(含完整源码)(组件引用查找工具实现笔记)

引言

Unity 编辑器内置了“Find References In Scene”功能,用于查找场景中特定组件的引用关系:选中组件右键,选择该选项,即可高亮所有使用该组件的 GameObject。 然而,该功能有时返回不相关结果(如所有脚本实例),且不支持一键全局查找或批量处理。 本工具扩展了这一功能,通过自定义 EditorWindow 实现一键查找:用户输入组件类型,工具扫描当前场景并列出所有引用该组件的 GameObject,支持高亮选中、导出列表和过滤。

工具基于 UnityEditor API(如 FindObjectsOfTypeSelection),适用于 Unity 2021.3+(推荐 2022.3 LTS),无需额外插件。原理:运行时反射获取组件类型,遍历场景 Hierarchy,收集引用关系。时间复杂度 O(n),n 为场景对象数,适用于中小型场景(大场景可分批处理)。


实现原理

  • 查找机制:使用 FindObjectsOfType<T>() 获取所有指定组件实例,然后遍历其 gameObject 收集引用路径。
  • 可视化:EditorWindow 提供输入框(组件类型名)和结果列表,支持点击高亮 GameObject。
  • 扩展性:支持过滤(如仅激活对象)、导出 JSON/CSV 和集成 Hierarchy 右键菜单。
  • 局限性:仅查找当前打开场景;对于预制体引用需额外处理(使用 PrefabUtility);不包括项目视图中的资产引用(需 AssetDatabase.FindAssets)。
  • 优化:缓存结果避免重复计算;异步搜索大场景避免编辑器卡顿。

完整源码

放置在 /Assets/Editor/ReferenceFinder.cs(创建 Editor 文件夹)。

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Linq;
using System.IO;

public class ReferenceFinder : EditorWindow
{
    private string componentName = "MeshRenderer";  // 默认组件类型
    private Vector2 scrollPosition;
    private List<GameObject> results = new List<GameObject>();
    private bool filterActiveOnly = true;
    private bool includePrefabs = false;
    private bool showResults = false;

    [MenuItem("Tools/Reference Finder")]
    public static void OpenWindow()
    {
        GetWindow<ReferenceFinder>("Component Reference Finder");
    }

    private void OnGUI()
    {
        GUILayout.Label("组件引用查找工具", EditorStyles.boldLabel);

        // 输入组件类型名
        componentName = EditorGUILayout.TextField("组件类型名", componentName);

        // 过滤选项
        filterActiveOnly = EditorGUILayout.Toggle("仅激活对象", filterActiveOnly);
        includePrefabs = EditorGUILayout.Toggle("包括预制体实例", includePrefabs);

        EditorGUILayout.Space();

        if (GUILayout.Button("查找引用"))
        {
            FindReferences();
        }

        if (showResults)
        {
            EditorGUILayout.Space();
            GUILayout.Label($"找到 {results.Count} 个引用", EditorStyles.miniBoldLabel);

            scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

            foreach (var go in results)
            {
                EditorGUILayout.BeginHorizontal();
                GUILayout.Label(go.name);
                if (GUILayout.Button("选中", GUILayout.Width(60)))
                {
                    Selection.activeGameObject = go;
                    EditorGUIUtility.PingObject(go);  // 高亮在 Hierarchy
                }
                EditorGUILayout.EndHorizontal();
            }

            EditorGUILayout.EndScrollView();

            EditorGUILayout.Space();
            if (GUILayout.Button("导出结果 (JSON)"))
            {
                ExportResults();
            }
        }
    }

    private void FindReferences()
    {
        results.Clear();
        showResults = true;

        // 获取组件类型
        Type componentType = GetComponentType(componentName);
        if (componentType == null)
        {
            EditorUtility.DisplayDialog("错误", "无效的组件类型名", "OK");
            return;
        }

        // 查找所有实例
        Component[] components = FindObjectsOfType(componentType);

        foreach (var comp in components)
        {
            GameObject go = comp.gameObject;

            // 过滤
            if (filterActiveOnly && !go.activeInHierarchy) continue;
            if (!includePrefabs && PrefabUtility.IsPartOfPrefabInstance(go)) continue;

            results.Add(go);
        }

        // 排序结果(按路径)
        results = results.OrderBy(go => GetGameObjectPath(go)).ToList();

        Repaint();  // 刷新窗口
    }

    private Type GetComponentType(string typeName)
    {
        // 支持内置组件和自定义脚本
        Type type = Type.GetType(typeName + ", UnityEngine");
        if (type == null)
        {
            type = Type.GetType(typeName + ", Assembly-CSharp");  // 项目脚本
        }
        return type;
    }

    private string GetGameObjectPath(GameObject go)
    {
        string path = go.name;
        Transform parent = go.transform.parent;
        while (parent != null)
        {
            path = parent.name + "/" + path;
            parent = parent.parent;
        }
        return path;
    }

    private void ExportResults()
    {
        string jsonPath = EditorUtility.SaveFilePanel("导出结果", "", "references.json", "json");
        if (string.IsNullOrEmpty(jsonPath)) return;

        List<string> paths = results.Select(go => GetGameObjectPath(go)).ToList();
        string json = JsonUtility.ToJson(new { references = paths });
        File.WriteAllText(jsonPath, json);

        EditorUtility.DisplayDialog("导出完成", "结果已导出到 " + jsonPath, "OK");
    }

    // 扩展:Hierarchy 右键菜单集成
    [MenuItem("GameObject/Find Component References", true)]
    private static bool ValidateFindReferences()
    {
        return Selection.activeGameObject != null;
    }

    [MenuItem("GameObject/Find Component References")]
    private static void FindReferencesFromSelection()
    {
        Component[] components = Selection.activeGameObject.GetComponents<Component>();
        if (components.Length == 0) return;

        // 示例:查找第一个组件的引用
        Type type = components[0].GetType();
        FindObjectsOfType(type);

        // 打开窗口显示结果
        OpenWindow();
    }
}

使用说明

  1. 安装:将源码保存为 ReferenceFinder.cs 在 Editor 文件夹。
  2. 打开工具:Tools > Reference Finder。
  3. 输入类型:如 “MeshRenderer”(内置)或 “MyCustomScript”(自定义)。
  4. 查找:点击“查找引用”,结果列表显示 GameObject 名,支持点击选中和高亮。
  5. 导出:结果可导出为 JSON 文件,便于分析。
  6. 右键集成:在 Hierarchy 选中 GameObject,右键 > Find Component References(查找其第一个组件的引用)。

扩展功能笔记

  • 异步查找:大场景添加 EditorCoroutine 避免卡顿:
  private IEnumerator AsyncFind(Type type)
  {
      Component[] components = FindObjectsOfType(type);
      yield return null;  // 分帧处理
      // 继续结果收集
  }
  • 预制体支持:使用 PrefabUtility.GetPrefabInstanceHandle 检查预制体引用。
  • 自定义过滤:添加搜索框过滤结果列表。
  • 集成开源工具:如 Reference Finder(),可扩展为项目引用查找。
  • 性能提示:大项目分场景查找;使用 Resources.FindObjectsOfTypeAll 包括非激活对象。

此工具简化了组件引用管理,适用于调试和优化。如果需要查找项目中资产引用,可扩展使用 AssetDatabase.FindAssets
“`

类似文章

发表回复

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