feat: 角色展示v1.2

This commit is contained in:
m0_75251201
2025-11-18 18:45:14 +08:00
parent 8fcbdc5649
commit 6cb89ba439
51 changed files with 1558 additions and 250 deletions

View File

@@ -0,0 +1,216 @@
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;
}
}
}