Files
Gen_Hack-and-Slash-Roguelit…/Client/Assets/Scripts/Managers/TemporaryAnimationManager.cs

428 lines
21 KiB
C#
Raw Permalink Normal View History

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可以工作
// 对于ScreenSpaceOverlayworldCamera可以为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>(); // 最常见的输入模块
}
}
}