Unity 查詢資源間的引用

陈侠云發表於2024-04-19

前言

最近音訊同事反饋說,給他們寫的編輯器中,音訊掛載在預製體當中了卻查詢不到Wwise的引用。
所以我回顧了之前的程式碼,提煉出比較核心的程式碼給大家分享下,但在專案後期,面對大量物件的情況下,其實效能還是不太行的,如果有人有更好的建議也可以在評論裡分享一下。
廢話不多說,開始!


SerializedObject和SerializedProperty

思路的核心是,找到gameObject中所有元件的引用物件,這裡Unity官方很貼心的給我們準備了2個利器,分別是SerializedObject和SerializedProperty,它倆的作用是編輯Unity物件上的可序列化欄位。

    /// <summary>
    /// 找到指定元件中所有的SerializedProperty屬性
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    private List<SerializedProperty> GetSerializedProperties(UnityEngine.Object obj)
    {
        var so = new SerializedObject(obj);
        so.Update();

        var result = new List<SerializedProperty>();
        SerializedProperty iterator = so.GetIterator();
        while (iterator.NextVisible(true))
        {
            SerializedProperty copy = iterator.Copy();
            if (iterator.isArray)
            {
                result.AddRange(GetSOArray(copy));
            }
            else
            {
                result.Add(copy);
            }
        }

        return result;
    }

    private List<SerializedProperty> GetSOArray(SerializedProperty property)
    {
        int size = property.arraySize;
        var result = new List<SerializedProperty>();

        for (int i = 0; i < size; i++)
        {
            var iterator = property.GetArrayElementAtIndex(i);
            var copy = property.Copy();
            
            if (iterator.isArray)
            {
                result.AddRange(GetSOArray(copy));   
            }
            else
            {
                result.Add(copy);
            }
        }

        return result;
    }

得到所有的SerializedProperty後,我們就可以判斷其中是否有我們需要的序列化物件了

Demo程式碼

// Copyright (c) 2024 陳俠雲. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;

public class SimpleFindRefTool : OdinEditorWindow
{
    private string findObjHash;
    private string[] files;
    private Dictionary<string, List<Info>> refGuid2Infos = new Dictionary<string, List<Info>>();
    
    [Serializable]
    private class Info
    {
        public string guid;
        public string assetPath;
        public string componentType;
        public string property;
    }
    
    [MenuItem("陳俠雲Tools/多執行緒加速資源查詢")]
    private static void ShowWindow()
    {
        var window = GetWindow<SimpleFindRefTool>();
        window.titleContent = new GUIContent("查詢資源引用");
        window.目錄 = $"{Application.dataPath}/多執行緒加速資源查詢/資源";
        window.Show();
    }

    #region 佈局

    [Sirenix.OdinInspector.ReadOnly] public string 目錄;
    [AssetsOnly] public UnityEngine.GameObject 資源;
    [Button]
    public void 搜尋()
    {
        refGuid2Infos.Clear();
        if (資源 == null)
        {
            Debug.LogError("查詢資源不存在");
            return;
        }

        findObjHash = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(資源));
        files = Directory.GetFiles(目錄, "*.*", SearchOption.AllDirectories)
            .Where(file => Path.GetExtension(file).ToLower() == ".prefab").ToArray();
        
        CollectSingleRes(files);
        if (refGuid2Infos.TryGetValue(findObjHash, out var infos))
        {
            for (int i = 0, icnt = infos.Count; i < icnt; i++)
            {
                var info = infos[i];
                Debug.Log($"預製體路徑:{info.assetPath} 元件:{info.componentType} 屬性:{info.property} 存在“{資源.name}”的引用");
            }
        }
        refGuid2Infos.Clear();
        Debug.Log("查詢完畢");
    }
    #endregion

    private void CollectSingleRes(string[] filePaths)
    {
        for (int i = 0; i < filePaths.Length; i++)
        {
            EditorUtility.DisplayProgressBar("資源查詢", $"查詢資源中({i}/{filePaths.Length})...", ((float)i / (float)filePaths.Length));
            string assetPath = filePaths[i].Replace(Application.dataPath + "/", "Assets\\");
            LoadSerialized(assetPath);
        }
        
        EditorUtility.ClearProgressBar();
    }

    /// <summary>
    /// 找到指定資源中所有引用到的GUID
    /// </summary>
    /// <param name="assetPath"></param>
    /// <returns></returns>
    private void LoadSerialized(string assetPath)
    {
        List<Info> list = new List<Info>();
        UnityEngine.Object assetData = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
        var gameObject = assetData as GameObject;
        if (gameObject != null)
        {
            Component[] components = gameObject.GetComponentsInChildren<Component>(true);
            for (int i = 0; i < components.Length; i++)
            {
                LoadSingleSerializedAsset(components[i], assetPath);
            }
        }
    }

    /// <summary>
    /// 找到指定元件中所有引用到的GUID
    /// </summary>
    /// <param name="component"></param>
    /// <returns></returns>
    private void LoadSingleSerializedAsset(Component component, string asssetPath)
    {
        List<SerializedProperty> properties = GetSerializedProperties(component);

        for (int i = 0, icnt = properties.Count; i < icnt; i++)
        {
            if (properties[i].propertyType != SerializedPropertyType.ObjectReference)
            {
                continue;
            }

            var refObj = properties[i].objectReferenceValue;
            if (refObj == null)
            {
                continue;
            }

            string refPath = AssetDatabase.GetAssetPath(refObj);
            string refGuid = AssetDatabase.AssetPathToGUID(refPath);

            if (!refGuid2Infos.TryGetValue(refGuid, out _))
            {
                refGuid2Infos[refGuid] = new List<Info>();
            }
            refGuid2Infos[refGuid].Add(new Info()
            {
                guid = refGuid,
                assetPath = asssetPath,
                componentType = component.GetType().Name,
                property = properties[i].name
            });
        }
    }

    /// <summary>
    /// 找到指定元件中所有的SerializedProperty屬性
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    private List<SerializedProperty> GetSerializedProperties(UnityEngine.Object obj)
    {
        var so = new SerializedObject(obj);
        so.Update();

        var result = new List<SerializedProperty>();
        SerializedProperty iterator = so.GetIterator();
        while (iterator.NextVisible(true))
        {
            SerializedProperty copy = iterator.Copy();
            if (iterator.isArray)
            {
                result.AddRange(GetSOArray(copy));
            }
            else
            {
                result.Add(copy);
            }
        }

        return result;
    }

    private List<SerializedProperty> GetSOArray(SerializedProperty property)
    {
        int size = property.arraySize;
        var result = new List<SerializedProperty>();

        for (int i = 0; i < size; i++)
        {
            var iterator = property.GetArrayElementAtIndex(i);
            var copy = property.Copy();
            
            if (iterator.isArray)
            {
                result.AddRange(GetSOArray(copy));   
            }
            else
            {
                result.Add(copy);
            }
        }

        return result;
    }
}

相關文章