using System; using Prefab; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using UnityEngine.UI; using Utils; namespace Managers { public class TemporaryAnimationManager : MonoSingleton { // 将包装图层改为由代码动态创建和管理 private GameObject _temporaryAnimationLevel; private GameObject _temporaryAnimationUILevel; [SerializeField] private TemporaryAnimatorImageUI temporaryAnimatorImageUIPrefab; [SerializeField] private TemporaryAnimatorSprite temporaryAnimatorSpritePrefab; [SerializeField] private TemporaryAnimatorText temporaryAnimatorTextPrefab; [SerializeField] private TemporaryAnimatorText temporaryAnimatorUITextPrefab; protected override void OnStart() { SceneManager.sceneLoaded += OnSceneLoaded; CreateAnimationLayersForCurrentScene(SceneManager.GetActiveScene(), LoadSceneMode.Single); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; // 在Manager销毁时,也销毁创建的图层,防止残留 if (_temporaryAnimationLevel != null) { Destroy(_temporaryAnimationLevel); } if (_temporaryAnimationUILevel != null) { Destroy(_temporaryAnimationUILevel); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { // 在新场景加载时,重新创建动画图层 CreateAnimationLayersForCurrentScene(scene, mode); } /// /// 为当前活动场景创建或重新创建临时动画的父级图层。 /// /// 当前加载的场景。 /// 场景加载模式。 private void CreateAnimationLayersForCurrentScene(Scene scene, LoadSceneMode mode) { // 销毁旧的图层(如果存在),确保每个场景都有独立的图层 // 注意:如果TemporaryAnimationManager是DontDestroyOnLoad, // 那么旧的图层可能还在,需要显式销毁。 if (_temporaryAnimationLevel != null) { Destroy(_temporaryAnimationLevel); } if (_temporaryAnimationUILevel != null) { Destroy(_temporaryAnimationUILevel); } // 创建非UI动画的包装图层 _temporaryAnimationLevel = new GameObject("TemporaryAnimations"); // 将其移动到当前加载的场景的根目录,使其成为场景的一部分 SceneManager.MoveGameObjectToScene(_temporaryAnimationLevel, scene); // 1. 获取或创建 Canvas var mainCanvas = GetOrCreateMainCanvas(); // 2. 创建 _temporaryAnimationUILevel GameObject _temporaryAnimationUILevel = new GameObject("TemporaryUIAnimations"); // 3. 将其设置为 Canvas 的子对象 // SetParent(parentTransform, worldPositionStay: false) 会将子对象的局部位置重置为 (0,0,0) // 这对于新的UI元素通常是期望的行为 _temporaryAnimationUILevel.transform.SetParent(mainCanvas.transform, false); // 4. 重置新创建的UI容器的局部变换 // 确保它相对于父级 Canvas 处于一个干净的初始状态 _temporaryAnimationUILevel.transform.localPosition = Vector3.zero; _temporaryAnimationUILevel.transform.localScale = Vector3.one; _temporaryAnimationUILevel.transform.localRotation = Quaternion.identity; } /// /// 生成一个非UI文本临时动画。 /// /// 动画的文本内容。 /// 动画在世界坐标系中的初始位置。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimation(string str, Vector3 position, float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { if (!_temporaryAnimationLevel) { Debug.LogError("TemporaryAnimationLevel is not initialized. Cannot generate non-UI animation."); return; } var textObj = Instantiate(temporaryAnimatorTextPrefab, _temporaryAnimationLevel.transform); textObj.transform.position = new Vector3(position.x,position.y); textObj.Init(str, fps); textObj.SetAnimationFunctions(xShift, yShift); textObj.lifeTime = lifeTime; textObj.gameObject.SetActive(true); } /// /// 生成一个非UI文本临时动画,可指定父级Transform。 /// /// 动画的文本内容。 /// 动画在世界坐标系中的初始位置。 /// 动画的父级Transform。如果为null,则使用默认的非UI动画层。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimation(string str, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { Transform actualParent = parent ?? _temporaryAnimationLevel?.transform; if (actualParent == null) { Debug.LogError("TemporaryAnimationLevel or specified parent is not initialized. Cannot generate non-UI animation."); return; } var textObj = Instantiate(temporaryAnimatorTextPrefab, actualParent); textObj.transform.position = position; // 外部传入的position应视为世界坐标 textObj.Init(str, fps); textObj.SetAnimationFunctions(xShift, yShift); textObj.lifeTime = lifeTime; textObj.gameObject.SetActive(true); } /// /// 生成一个非UI精灵临时动画。 /// /// 动画的精灵帧数组。 /// 动画在世界坐标系中的初始位置。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position,float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { if (!_temporaryAnimationLevel) { Debug.LogError("TemporaryAnimationLevel is not initialized. Cannot generate non-UI animation."); return; } var obj = Instantiate(temporaryAnimatorSpritePrefab, _temporaryAnimationLevel.transform); obj.transform.position = new Vector3(position.x,position.y); obj.Init(sprites, fps); obj.SetAnimationFunctions(xShift, yShift); obj.lifeTime = lifeTime; obj.gameObject.SetActive(true); } /// /// 生成一个非UI精灵临时动画,可指定父级Transform。 /// /// 动画的精灵帧数组。 /// 动画在世界坐标系中的初始位置。 /// 动画的父级Transform。如果为null,则使用默认的非UI动画层。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { Transform actualParent = parent ?? _temporaryAnimationLevel?.transform; if (actualParent == null) { Debug.LogError("TemporaryAnimationLevel or specified parent is not initialized. Cannot generate non-UI animation."); return; } var obj = Instantiate(temporaryAnimatorSpritePrefab, actualParent); obj.transform.position = position; // 外部传入的position应视为世界坐标 obj.Init(sprites, fps); obj.SetAnimationFunctions(xShift, yShift); obj.lifeTime = lifeTime; obj.gameObject.SetActive(true); } /// /// 生成一个UI文本临时动画。 /// 此重载将传入的 视为屏幕坐标 (如鼠标位置),并将其自动转换为Canvas局部坐标。 /// /// 动画的文本内容。 /// 动画在屏幕坐标系中的初始位置(如鼠标位置)。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimationUI(string str, Vector2 position,float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { if (!_temporaryAnimationUILevel) { Debug.LogError("TemporaryAnimationUILevel is not initialized. Cannot generate UI animation."); return; } var textObj = Instantiate(temporaryAnimatorUITextPrefab, _temporaryAnimationUILevel.transform); RectTransform canvasRectTransform = _temporaryAnimationUILevel.transform as RectTransform; if (canvasRectTransform != null) { // 获取Canvas组件,特别是RenderMode为ScreenSpaceCamera时需要worldCamera Canvas rootCanvas = canvasRectTransform.root.GetComponent(); Camera eventCamera = null; if (rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || rootCanvas.renderMode == RenderMode.WorldSpace) { eventCamera = rootCanvas.worldCamera; // 使用Canvas指定的相机 if (eventCamera == null) // 如果Canvas没有指定相机,尝试主相机 { eventCamera = Camera.main; } } if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, position, eventCamera, out var localPoint)) { textObj.rectTransform.anchoredPosition = localPoint; } else { textObj.rectTransform.anchoredPosition = position; Debug.LogWarning("Failed to convert screen point to local point for UI text animation. Using raw position."); } } else { textObj.rectTransform.anchoredPosition = position; Debug.LogWarning("TemporaryAnimationUILevel is not a RectTransform. Unable to convert screen point. Using raw position."); } textObj.Init(str, fps); textObj.SetAnimationFunctions(xShift, yShift); textObj.lifeTime = lifeTime; textObj.gameObject.SetActive(true); } /// /// 生成一个UI文本临时动画,可指定父级RectTransform。 /// 此重载将传入的 视为相对于父级RectTransform的 anchoredPosition。 /// /// 动画的文本内容。 /// 动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。 /// 动画的父级RectTransform。如果为null,则使用默认的UI动画层。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimationUI(string str, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { RectTransform actualParent = parent ?? (_temporaryAnimationUILevel?.transform as RectTransform); if (actualParent == null) { Debug.LogError("TemporaryAnimationUILevel or specified parent is not initialized or not a RectTransform. Cannot generate UI animation."); return; } var textObj = Instantiate(temporaryAnimatorUITextPrefab); textObj.rectTransform.SetParent(actualParent, false); // SetParent with worldPositionStay: false textObj.rectTransform.anchoredPosition = position; textObj.Init(str, fps); textObj.SetAnimationFunctions(xShift, yShift); textObj.lifeTime = lifeTime; textObj.gameObject.SetActive(true); } /// /// 生成一个UI精灵临时动画。 /// 此重载将传入的 视为屏幕坐标 (如鼠标位置),并将其自动转换为Canvas局部坐标。 /// /// 动画的精灵帧数组。 /// 动画在屏幕坐标系中的初始位置(如鼠标位置)。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position,float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { if (_temporaryAnimationUILevel == null) { Debug.LogError("TemporaryAnimationUILevel is not initialized. Cannot generate UI animation."); return; } var obj = Instantiate(temporaryAnimatorImageUIPrefab, _temporaryAnimationUILevel.transform); RectTransform canvasRectTransform = _temporaryAnimationUILevel.transform as RectTransform; if (canvasRectTransform != null) { // 获取Canvas组件,特别是RenderMode为ScreenSpaceCamera时需要worldCamera Canvas rootCanvas = canvasRectTransform.root.GetComponent(); Camera eventCamera = null; if (rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || rootCanvas.renderMode == RenderMode.WorldSpace) { eventCamera = rootCanvas.worldCamera; // 使用Canvas指定的相机 if (eventCamera == null) // 如果Canvas没有指定相机,尝试主相机 { eventCamera = Camera.main; } } Vector2 localPoint; if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, position, eventCamera, out localPoint)) { obj.rectTransform.anchoredPosition = localPoint; } else { obj.rectTransform.anchoredPosition = position; Debug.LogWarning("Failed to convert screen point to local point for UI sprite animation. Using raw position."); } } else { obj.rectTransform.anchoredPosition = position; Debug.LogWarning("TemporaryAnimationUILevel is not a RectTransform. Unable to convert screen point. Using raw position."); } obj.Init(sprites, fps); obj.SetAnimationFunctions(xShift, yShift); obj.lifeTime = lifeTime; obj.gameObject.SetActive(true); } /// /// 生成一个UI精灵临时动画,可指定父级RectTransform。 /// 此重载将传入的 视为相对于父级RectTransform的 anchoredPosition。 /// /// 动画的精灵帧数组。 /// 动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。 /// 动画的父级RectTransform。如果为null,则使用默认的UI动画层。 /// 动画的生命周期(秒)。 /// 动画帧率。 /// X轴位移函数。 /// Y轴位移函数。 public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func xShift = null, Func yShift = null) { RectTransform actualParent = parent ?? (_temporaryAnimationUILevel?.transform as RectTransform); if (actualParent == null) { Debug.LogError("TemporaryAnimationUILevel or specified parent is not initialized or not a RectTransform. Cannot generate UI animation."); return; } var obj = Instantiate(temporaryAnimatorImageUIPrefab); obj.rectTransform.SetParent(actualParent, false); // SetParent with worldPositionStay: false obj.rectTransform.anchoredPosition = position; obj.Init(sprites, fps); obj.SetAnimationFunctions(xShift, yShift); obj.lifeTime = lifeTime; obj.gameObject.SetActive(true); } /// /// 寻找场景中现有的 Canvas,如果不存在则创建一个新的。 /// 同时确保场景中存在 EventSystem。 /// /// 返回场景中的 Canvas 组件。 private static Canvas GetOrCreateMainCanvas() { var existingCanvas = FindFirstObjectByType(); if (existingCanvas != null) { EnsureEventSystemExists(); return existingCanvas; } // 创建 Canvas GameObject var canvasGO = new GameObject("Canvas"); canvasGO.layer = LayerMask.NameToLayer("UI"); var newCanvas = canvasGO.AddComponent(); newCanvas.renderMode = RenderMode.ScreenSpaceOverlay; // 确保有相机,以便RectTransformUtility.ScreenPointToLocalPointInRectangle可以工作 // 对于ScreenSpaceOverlay,worldCamera可以为null,但为了通用性和健壮性,这里设置为Camera.main, // 以防Canvas.renderMode将来调整为ScreenSpaceCamera或WorldSpace newCanvas.worldCamera = Camera.main; canvasGO.AddComponent(); canvasGO.AddComponent(); EnsureEventSystemExists(); return newCanvas; } /// /// 检查场景中是否存在 EventSystem,如果不存在则创建一个。 /// private static void EnsureEventSystemExists() { var existingEventSystem = FindFirstObjectByType(); if (existingEventSystem != null) return; var eventSystemGO = new GameObject("EventSystem"); eventSystemGO.AddComponent(); eventSystemGO.AddComponent(); // 最常见的输入模块 } } }