Files
DuckovMods/CharacterPreview/GameObjectUtils.cs
2025-11-18 18:45:14 +08:00

216 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}