using System.Collections.Generic; using UnityEngine; namespace CharacterPreview { public static class GameObjectUtils { /// /// 在指定根对象下根据路径查找子对象,包括隐藏对象。 /// 路径示例:"Parent/Child/Grandchild" 或 "MyObject" /// 如果路径以'/'开头,它会被视为相对于根对象。 /// /// 起始查找的根GameObject。 /// 要查找的对象的相对路径。 /// 找到的GameObject,如果未找到则返回null。 public static GameObject FindObjectByPath(GameObject rootObject, string path) { if (rootObject == null) { Debug.LogError("FindObjectByPath: rootObject cannot be null."); return null; } if (string.IsNullOrWhiteSpace(path)) { Debug.LogWarning("FindObjectByPath: Path is empty or whitespace. Returning the root object itself."); return rootObject; } // 规范化路径,移除多余的斜杠 var pathParts = path.Split(new char[] { '/' }, System.StringSplitOptions.RemoveEmptyEntries); var currentTransform = rootObject.transform; foreach (var part in pathParts) { Transform foundChild = null; // 迭代所有子对象(包括非激活的) // GetChild() 只获取激活状态的子对象,甚至不会获取非激活的 Transform。 // 更好的方法是迭代所有 Transform,检查 parent 是否是 currentTransform。 // 然而,Unity的API并没有直接提供一个GetChildren(includeInactive: true)的方法, // 所以我们需要遍历所有场景中的Transform或用递归查找。 // 对于层级较深或子对象较多的情况,这种遍历可能会比较慢。 // // 另一种常见但更低效的方法是使用 FindObjectsOfTypeAll() 然后过滤, // 但那会遍历整个场景。 // // 最直接且相对高效的方法是递归查找子Transform。 // 迭代当前 Transform 的所有直接子 Transform for (var i = 0; i < currentTransform.childCount; i++) { var child = currentTransform.GetChild(i); if (child.name == part) { foundChild = child; break; } } if (foundChild != null) { currentTransform = foundChild; } else { // 未找到当前路径部分对应的子对象 Debug.LogWarning( $"FindObjectByPath: Could not find '{part}' under '{currentTransform.name}' at path '{path}'."); return null; } } return currentTransform.gameObject; } /// /// (推荐使用) 更通用的方法:在任意指定Transform下根据相对路径查找子Transform,包括隐藏对象。 /// 路径示例:"Parent/Child/Grandchild" 或 "MyObject" /// /// 起始查找的根Transform。 /// 要查找的子Transform的相对路径。 /// 找到的Transform,如果未找到则返回null。 public static Transform FindTransformByRelativePath(Transform rootTransform, string relativePath) { if (rootTransform == null) { Debug.LogError("FindTransformByRelativePath: rootTransform cannot be null."); return null; } if (string.IsNullOrWhiteSpace(relativePath)) { Debug.LogWarning( "FindTransformByRelativePath: relativePath is empty or whitespace. Returning the rootTransform itself."); return rootTransform; } var pathParts = relativePath.Split(new char[] { '/' }, System.StringSplitOptions.RemoveEmptyEntries); var currentTransform = rootTransform; foreach (var partName in pathParts) { Transform foundChild = null; // GetChild(i) 方法返回的 Transform 即使其 GameObject.activeSelf 为 false,它仍然是存在的。 // 所以这种遍历是包括非激活对象的。 for (var i = 0; i < currentTransform.childCount; i++) { var child = currentTransform.GetChild(i); if (child.name == partName) { foundChild = child; break; } } if (foundChild != null) { currentTransform = foundChild; } else { Debug.LogWarning( $"FindTransformByRelativePath: Could not find '{partName}' under '{currentTransform.name}' (Full Path Attempted: {relativePath})."); return null; } } return currentTransform; } /// /// 在整个场景中根据完整路径查找GameObject,包括隐藏对象。 /// 注意:如果场景中有多个同名根对象,此方法可能只返回第一个找到的。 /// 如果路径以'/'开头,它会被视为相对于场景根目录的路径。 /// 例如: "MyRootObject/Child/Grandchild" /// /// 要查找的对象的完整路径。 /// 找到的GameObject,如果未找到则返回null。 public static GameObject FindObjectByFullPath(string fullPath) { if (string.IsNullOrWhiteSpace(fullPath)) { Debug.LogWarning("FindObjectByFullPath: Path is empty or whitespace."); return null; } // 规范化路径 var pathParts = fullPath.Split(new char[] { '/' }, System.StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length == 0) { return null; } var rootObjectName = pathParts[0]; // 查找所有根对象 (即没有父Transform的对象) // Unity没有直接提供获取所有root GameObjects的API // 我们可以通过迭代所有活跃的GameObject,并检查其parent是否为null来找到根对象。 // 但为了包含隐藏对象,我们需要更复杂的方法。 // // 更好的方法是使用 FindObjectsOfTypeAll() 找到所有Transform, // 然后过滤出parent为null的作为根对象。 // 注意:FindObjectsOfTypeAll 即使是静态方法,其效率也可能低于遍历已经知道的子对象。 // 在编辑器模式下,Resources.FindObjectsOfTypeAll() 可以获取所有对象。 // 在运行时,我们通常需要先有一个起点。 // 为了在运行时找到根对象(包括非激活的),我们可以先尝试 FindObjectOfType() // 或者遍历当前场景中的所有Transform,然后向上追溯到根。 // 最直接的做法是假设我们知道路径的第一个部分是根对象,然后从那里开始。 // 尝试通过 GameObject.Find() 或类似的编辑器工具去查找根对象 // GameObject.Find() 只查找激活的根对象,且只通过名称查找。 // GameObject.Find(rootObjectName) 即使找到了,也可能不会是整个路径的起点。 // 为了包含隐藏对象,我们需要遍历。 // 一个更可靠的方法是,先找到所有根Transform,然后对每个根Transform调用 FindTransformByRelativePath。 // 获取所有根Transform的方法: var rootTransforms = new List(); var allTransformsInScene = Resources.FindObjectsOfTypeAll(); // 注意:此方法在Editor中有效,在Build中会有些限制 // 在编辑器环境中,Resources.FindObjectsOfTypeAll 很好用。 // 在运行时,这个方法通常只返回在Resources文件夹中的对象,或者在场景中已有的对象,而不是所有已加载的对象。 // 对于运行时,更可靠的是遍历所有的场景: for (var i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) { var scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); if (scene.isLoaded) { foreach (var go in scene.GetRootGameObjects()) { // go 是根 GameObject,它可能是激活的或非激活的。 // GetRootGameObjects() 已经包含了非激活的根对象。 rootTransforms.Add(go.transform); } } } foreach (var rootT in rootTransforms) { if (rootT.name == rootObjectName) { // 如果路径只有一个部分,就是根对象本身 if (pathParts.Length == 1) { return rootT.gameObject; } // 构建子路径(移除根对象的名称) var relativeSubPath = string.Join("/", pathParts, 1, pathParts.Length - 1); var result = FindTransformByRelativePath(rootT, relativeSubPath); if (result != null) { return result.gameObject; } } } Debug.LogWarning( $"FindObjectByFullPath: Could not find any root object named '{rootObjectName}' or the full path '{fullPath}'."); return null; } } }