Unity Editor 扩展:查找缺失的 Image Sprite

在 Unity 开发过程中,缺失的 Sprite 引用(特别是在 UI 元素上)可能导致程序运行时出现问题,尤其是在使用 Image 组件时。当你拥有多个 Prefab 和大量的 UI 资源时,手动检查每个 Prefab 是否缺失了 Source Image 变得十分繁琐。

为了提高开发效率,今天我们来编写一个 Unity 编辑器脚本,通过查找 Prefab 中是否存在缺失的 Image 组件的 Sprite,帮助你快速定位缺失的资源。

目标

该脚本的主要目标是:

  • 遍历选中的文件夹中的 Prefab 文件。
  • 查找每个 Prefab 内的 Image 组件。
  • 如果 Image 组件的 Sprite 为空,则标记该节点为缺失,并记录其路径。
  • 在编辑器窗口中显示这些缺失的节点,供开发者定位和修复。

脚本解析 

1. 初始化窗口

我们首先通过 EditorWindow 创建一个自定义的 Unity 编辑器窗口。

public class MissingImageSpriteFinder : EditorWindow
{
    private Vector2 scrollPos;
    private List<ResultData> resultList = new List<ResultData>();

    private class ResultData
    {
        public GameObject prefab;
        public List<string> nodePaths = new List<string>();
    }
}

 这里,我们创建了一个 ResultData 类来存储每个 Prefab 和包含缺失 Sprite 的节点路径。在 MissingImageSpriteFinder 类中,我们定义了一个 resultList 来存储所有找到的结果。

2. 添加菜单项

我们通过 MenuItem 特性将功能添加到 Unity 编辑器的菜单中,方便开发者直接点击执行。

[MenuItem("Assets/Find Missing SourceImage", false, 49)]
public static void FindMissingImages()
{
    var window = GetWindow<MissingImageSpriteFinder>("Find Missing SourceImage Result");
    window.Search();
}

 此方法会在 Unity 编辑器中创建一个新的菜单项 查找 Missing 的 SourceImage,点击该菜单项时,会打开 MissingImageSpriteFinder 窗口并开始搜索缺失的 Sprite

3. 搜索逻辑

Search 方法中,我们首先清空结果列表,然后获取选中的文件夹路径,遍历其中的 Prefab 文件。

private void Search()
{
    resultList.Clear();
    string[] selectedGuids = Selection.assetGUIDs;

    foreach (string guid in selectedGuids)
    {
        string path = AssetDatabase.GUIDToAssetPath(guid);
        if (!AssetDatabase.IsValidFolder(path)) continue;

        string[] prefabPaths = Directory.GetFiles(path, "*.prefab", SearchOption.AllDirectories);

        foreach (string prefabPath in prefabPaths)
        {
            GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
            if (prefab == null) continue;

            Transform[] children = prefab.GetComponentsInChildren<Transform>(true);
            List<string> missingNodes = new List<string>();

            foreach (Transform t in children)
            {
                Image img = t.GetComponent<Image>();
                if (img != null && IsMissingReference(img, "m_Sprite"))
                {
                    string nodePath = GetTransformPath(t, prefab.transform);
                    missingNodes.Add(nodePath);
                }
            }

            if (missingNodes.Count > 0)
            {
                resultList.Add(new ResultData
                {
                    prefab = prefab,
                    nodePaths = missingNodes
                });
            }
        }
    }

    Repaint();
}

在这个方法中,我们做了以下几件事:

  • 获取当前选中的文件夹路径。
  • 查找该文件夹及其子文件夹中的所有 Prefab 文件。
  • 对每个 Prefab 文件进行处理,查找所有子节点中的 Image 组件。
  • 如果 Image 组件的 Sprite 属性为空,则认为是缺失的,并记录该节点的路径。

4. 界面显示

OnGUI 方法中,我们定义了自定义窗口的显示逻辑。

private void OnGUI()
{
    GUILayout.Label("查找结果", EditorStyles.boldLabel);

    if (resultList.Count == 0)
    {
        EditorGUILayout.HelpBox("未找到缺失的 SourceImage。", MessageType.Info);
        return;
    }

    scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

    foreach (var result in resultList)
    {
        EditorGUILayout.BeginVertical("box");
        EditorGUILayout.ObjectField("Prefab", result.prefab, typeof(GameObject), false);

        EditorGUILayout.LabelField("包含 Missing Sprite 的节点路径:");
        foreach (var path in result.nodePaths)
        {
            EditorGUILayout.LabelField(" - " + path);
        }

        if (GUILayout.Button("定位 Prefab", GUILayout.Width(100)))
        {
            Selection.activeObject = result.prefab;
            EditorGUIUtility.PingObject(result.prefab);
        }

        EditorGUILayout.EndVertical();
    }

    EditorGUILayout.EndScrollView();
}
  • 首先显示了一个标题标签。
  • 如果没有找到缺失的 Sprite,会显示一条提示消息。
  • 如果有找到缺失的 Sprite,则在滚动视图中列出每个 Prefab 和其包含缺失 Sprite 的节点路径。
  • 每个 Prefab 后面有一个按钮,点击后会自动选中该 Prefab 并在场景中高亮显示。

5. 辅助方法

IsMissingReferenceGetTransformPath 方法分别用于判断 Image 组件的 Sprite 是否为空,并获取节点相对 Prefab 的路径。

private bool IsMissingReference(Object obj, string propertyName)
{
    SerializedObject so = new SerializedObject(obj);
    SerializedProperty sp = so.FindProperty(propertyName);
    return sp != null && sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue != 0;
}

private string GetTransformPath(Transform current, Transform root)
{
    List<string> path = new List<string>();
    while (current != null && current != root)
    {
        path.Insert(0, current.name);
        current = current.parent;
    }
    return string.Join("/", path);
}
  • IsMissingReference 通过检查 Image 组件的 m_Sprite 属性是否为空,判断是否为缺失的引用。
  • GetTransformPath 通过遍历节点的父节点,构建从根节点到当前节点的路径。

完整代码

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

public class MissingImageSpriteFinder : EditorWindow
{
    private Vector2 scrollPos;
    private List<ResultData> resultList = new List<ResultData>();

    private class ResultData
    {
        public GameObject prefab;
        public List<string> nodePaths = new List<string>();
    }

    [MenuItem("Assets/Find Missing SourceImage", false, 49)]
    public static void FindMissingImages()
    {
        var window = GetWindow<MissingImageSpriteFinder>("Find Missing SourceImage Result");
        window.Search();
    }

    private void Search()
    {
        resultList.Clear();
        string[] selectedGuids = Selection.assetGUIDs;

        foreach (string guid in selectedGuids)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            if (!AssetDatabase.IsValidFolder(path)) continue;

            string[] prefabPaths = Directory.GetFiles(path, "*.prefab", SearchOption.AllDirectories);

            foreach (string prefabPath in prefabPaths)
            {
                GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
                if (prefab == null) continue;

                Transform[] children = prefab.GetComponentsInChildren<Transform>(true);
                List<string> missingNodes = new List<string>();

                foreach (Transform t in children)
                {
                    Image img = t.GetComponent<Image>();
                    if (img != null && IsMissingReference(img, "m_Sprite"))
                    {
                        string nodePath = GetTransformPath(t, prefab.transform);
                        missingNodes.Add(nodePath);
                    }
                }

                if (missingNodes.Count > 0)
                {
                    resultList.Add(new ResultData
                    {
                        prefab = prefab,
                        nodePaths = missingNodes
                    });
                }
            }
        }

        Repaint();
    }

    private void OnGUI()
    {
        GUILayout.Label("查找结果", EditorStyles.boldLabel);

        if (resultList.Count == 0)
        {
            EditorGUILayout.HelpBox("未找到缺失的 SourceImage。", MessageType.Info);
            return;
        }

        scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

        foreach (var result in resultList)
        {
            EditorGUILayout.BeginVertical("box");
            EditorGUILayout.ObjectField("Prefab", result.prefab, typeof(GameObject), false);

            EditorGUILayout.LabelField("包含 Missing Sprite 的节点路径:");
            foreach (var path in result.nodePaths)
            {
                EditorGUILayout.LabelField(" - " + path);
            }

            if (GUILayout.Button("定位 Prefab", GUILayout.Width(100)))
            {
                Selection.activeObject = result.prefab;
                EditorGUIUtility.PingObject(result.prefab);
            }

            EditorGUILayout.EndVertical();
        }

        EditorGUILayout.EndScrollView();
    }

    // 判断是否是 Missing 的引用
    private bool IsMissingReference(Object obj, string propertyName)
    {
        SerializedObject so = new SerializedObject(obj);
        SerializedProperty sp = so.FindProperty(propertyName);
        return sp != null && sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue != 0;
    }

    // 获取节点相对 prefab 的路径
    private string GetTransformPath(Transform current, Transform root)
    {
        List<string> path = new List<string>();
        while (current != null && current != root)
        {
            path.Insert(0, current.name);
            current = current.parent;
        }
        return string.Join("/", path);
    }
}

效果预览

右键选中文件夹 选择Find Missing SourceImage

效果如下

总结

通过编写这个自定义的 Unity 编辑器扩展,你可以高效地查找和修复缺失的 Image 组件的 Sprite 引用。它能够自动扫描选定的文件夹中的所有 Prefab,并定位其中的缺失引用,大大节省了手动检查的时间。

如果你的项目中包含大量的 UI Prefab 和资源,这个工具将是你提高生产力的重要助手。希望这篇博客对你有所帮助,快去试试吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小张不爱写代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值