216 lines
10 KiB
C#
216 lines
10 KiB
C#
|
|
using System.Collections.Generic;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
namespace CharacterPreview
|
|||
|
|
{
|
|||
|
|
public static class GameObjectUtils
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 在指定根对象下根据路径查找子对象,包括隐藏对象。
|
|||
|
|
/// 路径示例:"Parent/Child/Grandchild" 或 "MyObject"
|
|||
|
|
/// 如果路径以'/'开头,它会被视为相对于根对象。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="rootObject">起始查找的根GameObject。</param>
|
|||
|
|
/// <param name="path">要查找的对象的相对路径。</param>
|
|||
|
|
/// <returns>找到的GameObject,如果未找到则返回null。</returns>
|
|||
|
|
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 的所有直接子 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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// (推荐使用) 更通用的方法:在任意指定Transform下根据相对路径查找子Transform,包括隐藏对象。
|
|||
|
|
/// 路径示例:"Parent/Child/Grandchild" 或 "MyObject"
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="rootTransform">起始查找的根Transform。</param>
|
|||
|
|
/// <param name="relativePath">要查找的子Transform的相对路径。</param>
|
|||
|
|
/// <returns>找到的Transform,如果未找到则返回null。</returns>
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 在整个场景中根据完整路径查找GameObject,包括隐藏对象。
|
|||
|
|
/// 注意:如果场景中有多个同名根对象,此方法可能只返回第一个找到的。
|
|||
|
|
/// 如果路径以'/'开头,它会被视为相对于场景根目录的路径。
|
|||
|
|
/// 例如: "MyRootObject/Child/Grandchild"
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="fullPath">要查找的对象的完整路径。</param>
|
|||
|
|
/// <returns>找到的GameObject,如果未找到则返回null。</returns>
|
|||
|
|
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>() 找到所有Transform,
|
|||
|
|
// 然后过滤出parent为null的作为根对象。
|
|||
|
|
// 注意:FindObjectsOfTypeAll 即使是静态方法,其效率也可能低于遍历已经知道的子对象。
|
|||
|
|
// 在编辑器模式下,Resources.FindObjectsOfTypeAll<GameObject>() 可以获取所有对象。
|
|||
|
|
// 在运行时,我们通常需要先有一个起点。
|
|||
|
|
// 为了在运行时找到根对象(包括非激活的),我们可以先尝试 FindObjectOfType<GameObject>()
|
|||
|
|
// 或者遍历当前场景中的所有Transform,然后向上追溯到根。
|
|||
|
|
// 最直接的做法是假设我们知道路径的第一个部分是根对象,然后从那里开始。
|
|||
|
|
// 尝试通过 GameObject.Find() 或类似的编辑器工具去查找根对象
|
|||
|
|
// GameObject.Find() 只查找激活的根对象,且只通过名称查找。
|
|||
|
|
// GameObject.Find(rootObjectName) 即使找到了,也可能不会是整个路径的起点。
|
|||
|
|
// 为了包含隐藏对象,我们需要遍历。
|
|||
|
|
// 一个更可靠的方法是,先找到所有根Transform,然后对每个根Transform调用 FindTransformByRelativePath。
|
|||
|
|
// 获取所有根Transform的方法:
|
|||
|
|
var rootTransforms = new List<Transform>();
|
|||
|
|
var allTransformsInScene =
|
|||
|
|
Resources.FindObjectsOfTypeAll<Transform>(); // 注意:此方法在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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|