using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace ShrinkTutorial { internal static class ShrinkTutorialTargetUtility { public static Transform ResolveTarget(ShrinkTutorialStep step) { var target = step.targetMode switch { ShrinkTutorialTargetMode.Path => FindByHierarchyPath(step.targetPath), ShrinkTutorialTargetMode.AnchorId => ShrinkTutorialAnchorRegistry.Get(step.anchorId), _ => null }; return IsTargetAvailable(target) ? target : null; } public static bool TryBuildScreenRect(Transform targetTransform, out Rect rect) { rect = default; if (!IsTargetAvailable(targetTransform)) return false; if (targetTransform is RectTransform rectTransform) return TryBuildRectTransformScreenRect(rectTransform, out rect); if (targetTransform.TryGetComponent(out var renderer)) return TryBuildBoundsScreenRect(renderer.bounds, out rect); if (targetTransform.TryGetComponent(out var collider)) return TryBuildBoundsScreenRect(collider.bounds, out rect); var screenPoint = RectTransformUtility.WorldToScreenPoint(GetWorldCamera(), targetTransform.position); rect = new Rect(screenPoint.x - 24f, screenPoint.y - 24f, 48f, 48f); return true; } private static bool TryBuildRectTransformScreenRect(RectTransform rectTransform, out Rect rect) { var corners = new Vector3[4]; rectTransform.GetWorldCorners(corners); var camera = GetCanvasCamera(rectTransform); var min = RectTransformUtility.WorldToScreenPoint(camera, corners[0]); var max = min; for (var i = 1; i < corners.Length; i++) { var point = RectTransformUtility.WorldToScreenPoint(camera, corners[i]); min = Vector2.Min(min, point); max = Vector2.Max(max, point); } rect = Rect.MinMaxRect(min.x, min.y, max.x, max.y); return rect.width > 0f && rect.height > 0f; } private static bool TryBuildBoundsScreenRect(Bounds bounds, out Rect rect) { rect = default; var camera = GetWorldCamera(); if (!camera) return false; var corners = new List(8) { new(bounds.min.x, bounds.min.y, bounds.min.z), new(bounds.min.x, bounds.min.y, bounds.max.z), new(bounds.min.x, bounds.max.y, bounds.min.z), new(bounds.min.x, bounds.max.y, bounds.max.z), new(bounds.max.x, bounds.min.y, bounds.min.z), new(bounds.max.x, bounds.min.y, bounds.max.z), new(bounds.max.x, bounds.max.y, bounds.min.z), new(bounds.max.x, bounds.max.y, bounds.max.z) }; var min = new Vector2(float.MaxValue, float.MaxValue); var max = new Vector2(float.MinValue, float.MinValue); var hasVisiblePoint = false; foreach (var corner in corners) { var screenPoint = camera.WorldToScreenPoint(corner); if (screenPoint.z < 0f) continue; hasVisiblePoint = true; var point = new Vector2(screenPoint.x, screenPoint.y); min = Vector2.Min(min, point); max = Vector2.Max(max, point); } if (!hasVisiblePoint) return false; rect = Rect.MinMaxRect(min.x, min.y, max.x, max.y); return true; } private static Camera GetCanvasCamera(RectTransform rectTransform) { var canvas = rectTransform.GetComponentInParent(); if (!canvas || canvas.renderMode == RenderMode.ScreenSpaceOverlay) return null; return canvas.worldCamera ? canvas.worldCamera : Camera.main; } private static Camera GetWorldCamera() { if (Camera.main) return Camera.main; foreach (var camera in Camera.allCameras) { if (camera && camera.enabled) return camera; } return null; } private static bool IsTargetAvailable(Transform targetTransform) { return targetTransform && targetTransform.gameObject.activeInHierarchy; } private static Transform FindByHierarchyPath(string hierarchyPath) { if (string.IsNullOrWhiteSpace(hierarchyPath)) return null; var segments = hierarchyPath.Split('/'); for (var sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++) { var scene = SceneManager.GetSceneAt(sceneIndex); if (!scene.isLoaded) continue; foreach (var root in scene.GetRootGameObjects()) { if (TryMatchPath(root.transform, segments, 0, out var result)) return result; } } return null; } private static bool TryMatchPath(Transform current, string[] segments, int segmentIndex, out Transform result) { result = null; if (!current || segmentIndex >= segments.Length || current.name != segments[segmentIndex]) return false; if (segmentIndex == segments.Length - 1) { result = current; return true; } for (var i = 0; i < current.childCount; i++) { if (TryMatchPath(current.GetChild(i), segments, segmentIndex + 1, out result)) return true; } return false; } } }