428 lines
21 KiB
C#
428 lines
21 KiB
C#
using System;
|
||
using Prefab;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.UI;
|
||
using Utils;
|
||
|
||
namespace Managers
|
||
{
|
||
public class TemporaryAnimationManager : MonoSingleton<TemporaryAnimationManager>
|
||
{
|
||
// 将包装图层改为由代码动态创建和管理
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为当前活动场景创建或重新创建临时动画的父级图层。
|
||
/// </summary>
|
||
/// <param name="scene">当前加载的场景。</param>
|
||
/// <param name="mode">场景加载模式。</param>
|
||
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;
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个非UI文本临时动画。
|
||
/// </summary>
|
||
/// <param name="str">动画的文本内容。</param>
|
||
/// <param name="position">动画在世界坐标系中的初始位置。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimation(string str, Vector3 position, float lifeTime = 3, float fps = 3,
|
||
Func<float, float> xShift = null,
|
||
Func<float, float> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个非UI文本临时动画,可指定父级Transform。
|
||
/// </summary>
|
||
/// <param name="str">动画的文本内容。</param>
|
||
/// <param name="position">动画在世界坐标系中的初始位置。</param>
|
||
/// <param name="parent">动画的父级Transform。如果为null,则使用默认的非UI动画层。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimation(string str, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3,
|
||
Func<float, float> xShift = null,
|
||
Func<float, float> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个非UI精灵临时动画。
|
||
/// </summary>
|
||
/// <param name="sprites">动画的精灵帧数组。</param>
|
||
/// <param name="position">动画在世界坐标系中的初始位置。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||
Func<float, float> 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);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个非UI精灵临时动画,可指定父级Transform。
|
||
/// </summary>
|
||
/// <param name="sprites">动画的精灵帧数组。</param>
|
||
/// <param name="position">动画在世界坐标系中的初始位置。</param>
|
||
/// <param name="parent">动画的父级Transform。如果为null,则使用默认的非UI动画层。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||
Func<float, float> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个UI文本临时动画。
|
||
/// 此重载将传入的 <paramref name="position"/> 视为屏幕坐标 (如鼠标位置),并将其自动转换为Canvas局部坐标。
|
||
/// </summary>
|
||
/// <param name="str">动画的文本内容。</param>
|
||
/// <param name="position">动画在屏幕坐标系中的初始位置(如鼠标位置)。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimationUI(string str, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||
Func<float, float> 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<Canvas>();
|
||
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);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个UI文本临时动画,可指定父级RectTransform。
|
||
/// 此重载将传入的 <paramref name="position"/> 视为相对于父级RectTransform的 anchoredPosition。
|
||
/// </summary>
|
||
/// <param name="str">动画的文本内容。</param>
|
||
/// <param name="position">动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
|
||
/// <param name="parent">动画的父级RectTransform。如果为null,则使用默认的UI动画层。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimationUI(string str, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||
Func<float, float> 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);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 生成一个UI精灵临时动画。
|
||
/// 此重载将传入的 <paramref name="position"/> 视为屏幕坐标 (如鼠标位置),并将其自动转换为Canvas局部坐标。
|
||
/// </summary>
|
||
/// <param name="sprites">动画的精灵帧数组。</param>
|
||
/// <param name="position">动画在屏幕坐标系中的初始位置(如鼠标位置)。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||
Func<float, float> 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<Canvas>();
|
||
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);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成一个UI精灵临时动画,可指定父级RectTransform。
|
||
/// 此重载将传入的 <paramref name="position"/> 视为相对于父级RectTransform的 anchoredPosition。
|
||
/// </summary>
|
||
/// <param name="sprites">动画的精灵帧数组。</param>
|
||
/// <param name="position">动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
|
||
/// <param name="parent">动画的父级RectTransform。如果为null,则使用默认的UI动画层。</param>
|
||
/// <param name="lifeTime">动画的生命周期(秒)。</param>
|
||
/// <param name="fps">动画帧率。</param>
|
||
/// <param name="xShift">X轴位移函数。</param>
|
||
/// <param name="yShift">Y轴位移函数。</param>
|
||
public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
|
||
Func<float, float> 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);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 寻找场景中现有的 Canvas,如果不存在则创建一个新的。
|
||
/// 同时确保场景中存在 EventSystem。
|
||
/// </summary>
|
||
/// <returns>返回场景中的 Canvas 组件。</returns>
|
||
private static Canvas GetOrCreateMainCanvas()
|
||
{
|
||
var existingCanvas = FindFirstObjectByType<Canvas>();
|
||
if (existingCanvas != null)
|
||
{
|
||
EnsureEventSystemExists();
|
||
return existingCanvas;
|
||
}
|
||
|
||
// 创建 Canvas GameObject
|
||
var canvasGO = new GameObject("Canvas");
|
||
canvasGO.layer = LayerMask.NameToLayer("UI");
|
||
var newCanvas = canvasGO.AddComponent<Canvas>();
|
||
newCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
|
||
// 确保有相机,以便RectTransformUtility.ScreenPointToLocalPointInRectangle可以工作
|
||
// 对于ScreenSpaceOverlay,worldCamera可以为null,但为了通用性和健壮性,这里设置为Camera.main,
|
||
// 以防Canvas.renderMode将来调整为ScreenSpaceCamera或WorldSpace
|
||
newCanvas.worldCamera = Camera.main;
|
||
|
||
canvasGO.AddComponent<CanvasScaler>();
|
||
canvasGO.AddComponent<GraphicRaycaster>();
|
||
EnsureEventSystemExists();
|
||
return newCanvas;
|
||
}
|
||
/// <summary>
|
||
/// 检查场景中是否存在 EventSystem,如果不存在则创建一个。
|
||
/// </summary>
|
||
private static void EnsureEventSystemExists()
|
||
{
|
||
var existingEventSystem = FindFirstObjectByType<EventSystem>();
|
||
if (existingEventSystem != null) return;
|
||
var eventSystemGO = new GameObject("EventSystem");
|
||
eventSystemGO.AddComponent<EventSystem>();
|
||
eventSystemGO.AddComponent<StandaloneInputModule>(); // 最常见的输入模块
|
||
}
|
||
}
|
||
}
|