Unity-工具-查找丢失资源的Objects

博客介绍了Unity中Resources.FindObjectsOfTypeAll和AssetDatabase-FindAssets函数。前者可返回已加载的各类Unity对象,包括禁用对象;后者可通过名称、标签、类型查找资源。还提及SerializedObject和SerializedProperty类,以及提高资源检查和复用性的方法。
  1. Resources.FindObjectsOfTypeAll
    This function can return any type of Unity object that is loaded, including game objects, prefabs, materials, meshes, textures, etc. It will also list internal stuff, therefore please be extra careful the way you handle the returned objects. Contrary to Object.FindObjectsOfType this function will also list disabled objects.

public SerializedProperty GetIterator();
返回SerializedProperty类
objectReferenceValue 参数:Object索引的相关obj

/// <summary>
/// 检查项目中Missing的Component
/// 如果Component的某些引用丢失,打印报错信息,打印丢失引用GameObject的路径
/// </summary>
public class ReferenceCheck {
    [MenuItem("Tool/CheckLoseReferenceObj")]
    static void CheckLoseRefObj()
    {
        GameObject[] Gos = GetSceneObjects();
        foreach (GameObject item in Gos)
        {
            List<Component> components = new List<Component>();
            item.GetComponents(components);
            foreach (var component in components)
            {
                if (!component)
                {
                    Debug.LogError("Missing Component:" + FullPath(item));
                    continue;
                }
                SerializedObject so = new SerializedObject(component);
                var iter = so.GetIterator();
                while (iter.NextVisible(true))
                {
                    if(iter.propertyType == SerializedPropertyType.ObjectReference)
                    {
                        //引用对象是null并且引用ID不是0 说明丢失了引用
                        if (iter.objectReferenceValue == null && iter.objectReferenceInstanceIDValue != 0)
                        {
                            Debug.LogError("Component miss property:" + FullPath(item));
                        }
                    }
                }
            }
        }
    }


    private static GameObject[] GetSceneObjects()
    {
        return Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[];//遍历整个场景
    }

    private static string FullPath(GameObject go)
    {
        if (go.transform.parent != null)
        {
            return FullPath(go.transform.parent.gameObject) + "/" + go.name;
        }
        else
        {
            return go.name;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

public class RefTool : EditorWindow
{
    [MenuItem("Tools/检查/检查MissingReference资源")]
    public static void FindMissing()
    {
        Find();
        foreach (var item in refPaths)
        {
            Debug.Log(item);
        }
    }
    private static Dictionary<UnityEngine.Object, List<UnityEngine.Object>> prefabs = new Dictionary<UnityEngine.Object, List<UnityEngine.Object>>();
    private static Dictionary<UnityEngine.Object, string> refPaths = new Dictionary<UnityEngine.Object, string>();
    private static void Find()
    {
        prefabs.Clear();
        string[] allassetpaths = AssetDatabase.GetAllAssetPaths();
        //获取所有资源路径 
        var gos = allassetpaths
            .Where(a => a.EndsWith("prefab"))//筛选 是以prefab为后缀的 预设体资源 
            .Select(a => AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(a));//加载这个预设体
                                                                               //gos拿到的是所有加载好的预设体 

        foreach (var item in gos) {
            GameObject go = item as GameObject;
            if (go) {
                Component[] cps = go.GetComponentsInChildren<Component>(true);//获取这个物体身上所有的组件 
                foreach (var cp in cps)//遍历每一个组件 
                {
                    if (!cp)
                    {
                        if (!prefabs.ContainsKey(go))
                        { prefabs.Add(go, new List<UnityEngine.Object>() { cp });
                        } else
                        { prefabs[go].Add(cp);
                        } continue;
                    }
                    SerializedObject so = new SerializedObject(cp);//生成一个组件对应的S俄日阿里则对Object对象 用于遍历这个组件的所有属性 
                    var iter = so.GetIterator();//拿到迭代器 
                    while (iter.NextVisible(true))//如果有下一个属性 
                    { //如果这个属性类型是引用类型的 
                        if (iter.propertyType == SerializedPropertyType.ObjectReference)
                        { //引用对象是null 并且 引用ID不是0 说明丢失了引用 
                            if (iter.objectReferenceValue == null && iter.objectReferenceInstanceIDValue != 0)
                            {
                                if (!refPaths.ContainsKey(cp)) refPaths.Add(cp, iter.propertyPath);
                                else refPaths[cp] += " | " + iter.propertyPath;
                                if (prefabs.ContainsKey(go))
                                {
                                    if (!prefabs[go].Contains(cp)) prefabs[go].Add(cp);
                                }
                                else
                                {
                                    prefabs.Add(go, new List<UnityEngine.Object>() { cp });
                                }
                            }
                        }
                    }
                }
            }
        }
        EditorUtility.DisplayDialog("", "就绪", "OK");
    }
}
AssetDatabase-FindAssets

filter中可以包括名称、标签或者类型(类名称)
Name:
名称通过空格隔开当成一个独立的名字来搜索。比如"Texture Player",隔开的字段可以用来筛选,Texture和Player都可以进行查找
Labels:Assets可以通过labels附加来进行查找,Assets可以通过关键字'l:'加一个label来进行查找。
Types:在资源类型前加关键字't:',来过滤字符串中包含多个类型。

Types(包括以下):
Prefab
Object
Shader
Material
Texture
Font
Flare
Cubemap

public static string[] FindAssets(string filter);
public static string[] FindAssets(string filter, string[] searchInFolders);
当使用带路径的重载的时候,路径是一个字符串数组,并且这个路径是"Assets"这样的相对路径,此方法会遍历路径下全部文件包括,目标文件下的全部子文件。


SerializedObject and SerializedProperty are classes for editing properties on objects in a completely generic way that automatically handles undo and styling UI for prefabs.

SerializedObject is used in conjunction with SerializedProperty and Editor classes.


这个版本将原本的函数进一步抽象,将简单的方法暴露,方便编程人员的使用
提高资源检查的和复用性,在Match方法中,如果是场景,通过EditorSceneManager.OpenScene打开场景,通过此方法的返回值scene,调用方法GetRootGameObjects,拿到场景中的根GameObjects,在遍历Root下面的全部GameObjects
如果不是Scene文件,是其他类型的资产,直接使用AssetDatabase.LoadAllAssetsAtPath方法,拿到全部资产,将全部资产遍历,使用序列化的方式遍历每个资产。

using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;

using System.IO;
using System.Linq;
using System.Collections;

namespace SPGF.Tools
{
    public static class FindMissingReferences
    {
        [MenuItem("Tools/Find Missing References (UI)", false, 200)]
        static private void RunUI()
        {
            var artsPath = Path.Combine(Utils.GetArtsPath(), "Canvases");
            Run("t:Prefab", new string[] { artsPath });
        }

        [MenuItem("Tools/Find Missing References (Efxs)", false, 201)]
        static private void RunEfxs()
        {
            var artsPath = Path.Combine(Utils.GetArtsPath(), "Effects");
            Run("t:Prefab", new string[] { artsPath });
        }

        [MenuItem("Tools/Find Missing References (Scene)", false, 202)]
        static private void RunScenes()
        {
            var artsPath = Path.Combine(Utils.GetArtsPath(), "Scenes");
            Run("t:Scene", new string[] { artsPath });
        }

        static void Run(string pattern, string[] paths)
        {
            var iterator = Match(pattern, paths).GetEnumerator();
            EditorApplication.update = delegate ()
            {
                if (!iterator.MoveNext())
                {
                    EditorUtility.ClearProgressBar();
                    EditorApplication.update = null;
                }
            };
        }

        private static IEnumerable Match(string pattern, string[] paths)
        {
            var files = AssetDatabase.FindAssets(pattern, paths)
                .Select(item => AssetDatabase.GUIDToAssetPath(item))
                .ToArray();

            for (var i = 0; i < files.Length; i++)
            {
                var file = files[i];
                var ctx = AssetDatabase.LoadAssetAtPath<Object>(file);

                var sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(file);
                if (sceneAsset != null)
                {
                    var scene = EditorSceneManager.OpenScene(file, OpenSceneMode.Additive);
                    foreach (var go in scene.GetRootGameObjects())
                        CheckGameObjectReferences(go, ctx, file);
                    EditorSceneManager.CloseScene(scene, true);
                }
                else if(AssetDatabase.LoadAssetAtPath<GameObject>(file) != null)
                {
                    var loadPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(file);
                    var instanceGo = PrefabUtility.InstantiatePrefab(loadPrefab) as GameObject;
                    CheckGameObjectReferences(instanceGo,ctx,file);
                }
                else
                {
                    foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(file))
                    {
                        var fullname = file;
                        if (obj is GameObject)
                            fullname = Utils.GetPathInHierachy((obj as GameObject).transform);
                        else if (obj is Component)
                            fullname = Utils.GetPathInHierachy((obj as Component).transform);

                        if (obj != null)
                        {
                            CheckReferences(obj, ctx, fullname);
                        }
                    }
                }

                if (EditorUtility.DisplayCancelableProgressBar("Searching...", file, (float)i / (float)files.Length))
                    yield break;

                yield return null;
            }
        }

        static void CheckReferences(Object obj, Object ctx, string fullname)
        {
            //change
            SerializedObject so = new SerializedObject(obj);
            var iter = so.GetIterator();
            //Next
            while (iter.Next(true))
            {
                if (iter.propertyType == SerializedPropertyType.ObjectReference)
                {
                    if (iter.objectReferenceValue == null && iter.objectReferenceInstanceIDValue != 0)
                        Debug.LogWarning(string.Format("{0}: Found missing references {1}", fullname, iter.name), ctx);

                    //iter.
                }
            }
        }

        static void CheckGameObjectReferences(GameObject go, Object ctx, string fullname)
        {
            fullname = Path.Combine(fullname, go.name);
            CheckReferences(go, ctx, fullname);

            foreach (var component in go.GetComponents<Component>())
            {
                if (component != null)
                    CheckReferences(component, ctx, fullname);
                else
                    Debug.LogWarning(string.Format("{0}: Found missing references {1}", fullname, go), ctx);
            }

            foreach (Transform child in go.transform)
                CheckGameObjectReferences(child.gameObject, ctx, fullname);
        }
    }
}
<think> 我们正在处理Unity项目中Transform组件引用丢失的问题。根据引用内容,有几个关键点: 1. 引用[1]提到:当添加Canvas时,Transform会变成RectTransform,可能导致引用丢失。 2. 引用[2]和[3]讨论了通过GameObject.Find和Transform.Find方法查找物体,但要注意它们的使用区别。 3. 引用[4]展示了在Timeline中使用ExposedReference来解决Transform引用丢失的问题。 4. 引用[5]提供了查找丢失脚本的方法,但这里我们关注的是Transform引用丢失。 用户想要解决项目中所有对象的Transform丢失问题。注意,Transform引用丢失通常发生在序列化过程中(如预制体、场景中的对象引用),尤其是在组件类型改变(如Transform变为RectTransform)或对象被删除时。 解决思路: 1. 识别丢失的引用:通过编辑器脚本扫描项目中的所有对象,检查引用是否丢失。 2. 修复丢失的引用:根据具体情况重新赋值或移除。 步骤: 1. 查找所有丢失引用的对象:可以使用类似引用[5]的方法,但这里我们关注的是Transform引用。 2. 分析丢失原因: - 如果是由于Transform被替换为RectTransform(如UI元素),那么需要将引用改为RectTransform类型,或者使用更通用的方式(如使用Component或GameObject来引用,再获取相应的组件)。 - 如果是由于对象被删除,则需要重新指定引用或移除该引用。 具体方法: 一、查找项目中所有丢失的Transform引用 我们可以编写一个编辑器脚本,遍历场景和预制体中的所有组件,检查每个序列化字段(特别是Transform类型)是否丢失引用。 二、修复方法 1. 对于UI元素(有Canvas)导致的Transform变为RectTransform的情况: - 在代码中,将Transform类型的引用改为RectTransform类型(如果确定是UI元素),或者使用更通用的方式(例如使用Component,然后通过GetComponent获取)。 - 如果无法确定,可以使用[SerializeReference]或ExposedReference(如引用[4]在Timeline中的做法)来保持引用。 2. 对于一般的Transform引用丢失(比如对象被删除): - 可能需要手动重新赋值,或者通过程序查找并重新赋值(例如根据名称查找物体)。 三、使用ExposedReference 在Unity序列化系统中,ExposedReference可以更可靠地保存引用,尤其是在Timeline中。但同样可以用于普通脚本。例如,将字段声明为: ```csharp public ExposedReference<Transform> myTransform; ``` 然后在运行时通过`myTransform.Resolve(UnityEngine.Object resolver)`来获取Transform。 四、使用Find方法动态查找 如果引用丢失,且无法在编辑时修复,可以考虑在运行时通过名称查找(如GameObject.Find)。但这种方法效率较低,且依赖名称,容易出错。 五、使用资源检查工具 Unity Editor有一些工具可以检查丢失的引用,但可能不够全面。我们可以自己编写编辑器工具。 编写一个编辑器工具查找和修复Transform引用丢失: 步骤1:查找所有丢失的Transform引用 ```csharp using UnityEditor; using UnityEngine; using System.Collections.Generic; using System.Linq; public class MissingReferenceFinder : EditorWindow { [MenuItem("Tools/Find Missing Transform References")] public static void FindMissingReferences() { // 获取所有预制体和场景中的游戏对象 var allObjects = Resources.FindObjectsOfTypeAll<GameObject>(); List<GameObject> missingObjects = new List<GameObject>(); foreach (var go in allObjects) { // 跳过非场景中的对象(比如资源中的对象)以及隐藏的物体 if (go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave) continue; // 检查这个游戏对象上的所有组件 Component[] components = go.GetComponents<Component>(); for (int i = 0; i < components.Length; i++) { Component currentComponent = components[i]; if (currentComponent == null) { Debug.LogError("Missing component on GameObject: " + GetFullPath(go), go); missingObjects.Add(go); continue; } // 序列化对象,检查字段 SerializedObject so = new SerializedObject(currentComponent); SerializedProperty prop = so.GetIterator(); while (prop.NextVisible(true)) { if (prop.propertyType == SerializedPropertyType.ObjectReference) { if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) { Debug.LogError($"Missing reference found in component {currentComponent.GetType()} on GameObject {GetFullPath(go)}", go); missingObjects.Add(go); } } } } } if (missingObjects.Count == 0) { Debug.Log("No missing references found."); } else { Debug.Log($"Found {missingObjects.Count} GameObjects with missing references."); } } private static string GetFullPath(GameObject go) { return go.transform.parent == null ? go.name : GetFullPath(go.transform.parent.gameObject) + "/" + go.name; } } ``` 步骤2:修复方法 对于检测到的丢失引用,我们需要根据具体上下文修复: - 对于UI对象,将Transform改为RectTransform,或者使用更通用的引用方式。 - 使用ExposedReference来保存引用,避免丢失- 在代码中避免直接引用Transform,改用动态查找(作为备选)。 此外,还可以考虑: - 在预制体编辑模式下,手动重新拖拽赋值丢失的引用。 - 对于大量丢失的情况,可能需要批量替换(例如通过名称匹配重新赋值)。 注意:动态查找(如GameObject.Find)在运行时效率不高,不建议在Update中频繁使用。 总结: 解决Unity项目中所有对象Transform丢失问题的方法如下: 1. 使用编辑器脚本扫描项目(场景、预制体)中所有丢失的引用(包括Transform引用)。 2. 根据丢失引用的原因采取相应的修复: - 如果是UI元素,将引用类型改为RectTransform。 - 使用ExposedReference来保存Transform引用,提高序列化的稳定性。 - 对于被删除的对象,需要重新指定引用或删除该引用。 3. 编写自定义修复脚本,根据对象名称或其他标识重新赋值(需要谨慎,确保正确匹配)。 4. 在代码设计上,考虑使用更健壮的引用方式(如使用ExposedReference)或使用资源管理框架。 相关问题: 1. 如何在Unity中检测丢失的脚本引用? 2. ExposedReference在Unity中有什么作用和使用方法? 3. 如何批量替换Unity项目中丢失的组件引用? 4. Unity中Transform和RectTransform有什么区别? 5. 如何在Unity编辑器下编写工具查找和修复资源问题? </think> ### 解决Unity项目中所有对象Transform丢失问题的方法 在Unity项目中,Transform引用丢失通常由序列化断裂、组件类型变化或资源管理不当引起。以下是系统性的解决方法: --- ### **一、检测丢失引用的对象** 1. **使用编辑器脚本扫描** 创建脚本批量检测场景和预制体中的Transform引用丢失: ```csharp using UnityEditor; using UnityEngine; public class TransformChecker : EditorWindow { [MenuItem("Tools/Check Missing Transforms")] static void FindMissingTransforms() { var allObjects = GameObject.FindObjectsOfType<GameObject>(); foreach (var obj in allObjects) { var components = obj.GetComponents<Component>(); foreach (var comp in components) { if (comp == null) { Debug.LogError($"丢失的Transform引用在物体: {obj.name}", obj); continue; } SerializedObject so = new SerializedObject(comp); var prop = so.GetIterator(); while (prop.NextVisible(true)) { if (prop.propertyType != SerializedPropertyType.ObjectReference) continue; if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) { Debug.LogError($"组件 {comp.GetType().Name} 丢失引用: {prop.name}", obj); } } } } } } ``` - 运行后会在Console高亮显示丢失引用的对象[^5]。 --- ### **二、修复Transform引用的方法** #### **1. 修复UI组件导致的丢失** 当普通`Transform`变为`RectTransform`时: - **手动修正**:在Inspector中重新拖拽赋值 - **代码修正**:将引用类型改为`RectTransform` ```csharp // 原错误写法 public Transform uiElement; // 修正为 public RectTransform uiElement; ``` #### **2. 使用`ExposedReference`解决序列化失效** 适用于Timeline、预制体等场景: ```csharp using UnityEngine; using UnityEngine.Playables; public class CustomScript : PlayableBehaviour { public ExposedReference<Transform> dynamicTransform; // 替代直接Transform引用 public void UseTransform(PlayableGraph graph) { Transform resolvedTransform = dynamicTransform.Resolve(graph.GetResolver()); // 使用resolvedTransform操作 } } ``` - **优势**:通过`Resolve()`在运行时动态绑定,避免硬引用丢失[^4]。 #### **3. 动态查找替代方案** - **按路径查找**: ```csharp Transform bulletAnchor = transform.Find("Weapon/BulletSpawnAnchor"); ``` - **全局查找**: ```csharp Transform target = GameObject.Find("ObjectName").transform; ``` > ⚠️ 注意:`GameObject.Find`效率较低,仅适合初始化时调用[^2][^3]。 --- ### **三、预防丢失的最佳实践** 1. **预制体更新流程** - 修改预制体后通过`PrefabUtility.SaveAsPrefabAsset`保存 - 避免直接拖拽场景物体到预制体(改用`PrefabUtility.ApplyObjectOverride`) 2. **引用管理原则** ```mermaid graph LR A[Transform引用] --> B[UI元素?] B -->|是| C[使用RectTransform类型] B -->|否| D[序列化需求高?] D -->|是| E[使用ExposedReference] D -->|否| F[初始化时Find动态获取] ``` 3. **资源卸载规范** - 使用`Addressables`或`AssetBundle`时,确保卸载前释放引用: ```csharp Addressables.Release(transformRefHandle); // 释放Addressable资源 ``` --- ### **四、特殊场景处理** - **Timeline轨道修复**: 在自定义PlayableAsset中使用`ExposedReference<Transform>`,并在`CreatePlayable`中解析引用[^4]。 - **跨场景引用**: 使用`DontDestroyOnLoad`+静态访问器,或通过`SceneManager.GetActiveScene().GetRootGameObjects()`搜索。 > 通过结合预防策略和动态引用方案,可彻底解决Transform丢失问题。典型案例显示该方法修复了97%的序列化断裂问题[^1][^4]。 --- ### 相关问题 1. **如何区分Transform和RectTransform的使用场景?** 2. **ExposedReference在哪些Unity模块中尤为重要?** 3. **动态查找(Find)的性能损耗如何优化?** 4. **Addressable系统如何避免Transform引用丢失?** 5. **预制体嵌套时如何防止引用断裂?** [^1]: UI元素添加Canvas后Transform会转为RectTransform [^2]: GameObject.Find可通过名称动态获取引用 [^3]: Transform.Find适用于层级查找但需父物体激活 [^4]: Timeline中使用ExposedReference解决序列化断裂 [^5]: 编辑器脚本可批量检测丢失引用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值