2025-09-02 11:08:15 +08:00
|
|
|
|
using System;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace Base
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 抽象基类,用于封装通用动画逻辑。
|
|
|
|
|
/// 所有的动画播放器都应继承自该类,并实现其抽象方法来控制具体的显示组件。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public abstract class BaseAnimator : MonoBehaviour, ITick
|
|
|
|
|
{
|
|
|
|
|
// 通用公开字段(可在编辑器中设置)
|
2025-09-06 12:25:55 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 动画播放的精灵序列。
|
|
|
|
|
/// </summary>
|
|
|
|
|
[SerializeField] protected Sprite[] _sprites;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 每秒帧数,控制动画播放速度。
|
|
|
|
|
/// </summary>
|
|
|
|
|
[SerializeField] protected float _fps = 2;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 动画暂停时显示的静态精灵。
|
|
|
|
|
/// 如果未设置,则默认保持当前帧。
|
|
|
|
|
/// </summary>
|
|
|
|
|
[SerializeField] protected Sprite _staticSprite;
|
2025-09-02 11:08:15 +08:00
|
|
|
|
|
|
|
|
|
// 通用内部状态
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 指示动画是否处于暂停状态。
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected bool _isPaused;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 用于跟踪当前帧播放时间的计时器。
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected float _frameTimer;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 当前正在显示的精灵帧索引。
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected int _currentFrameIndex;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 指示动画是否为一次性播放模式。
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected bool _isOneShot;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 抽象方法:子类必须实现此方法以获取并验证其特有的显示组件。
|
|
|
|
|
/// 例如:SpriteRenderer、Image等。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected abstract void ValidateComponent();
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 抽象方法:子类必须实现此方法以将给定的精灵设置到实际显示组件上。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="sprite">要显示的精灵。</param>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected abstract void SetDisplaySprite(Sprite sprite);
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unity生命周期方法:在脚本实例被加载时调用。
|
|
|
|
|
/// 主要用于初始化组件和动画的第一帧。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected virtual void Awake()
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 子类获取并验证其显示组件
|
|
|
|
|
ValidateComponent();
|
|
|
|
|
// 初始化动画的第一帧
|
|
|
|
|
ValidateStartFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unity生命周期方法:在第一次帧更新之前调用。
|
|
|
|
|
/// 用于将当前动画器添加到全局计时器。
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void Start()
|
|
|
|
|
{
|
|
|
|
|
Clock.AddTick(this);
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unity生命周期方法:在MonoBehaviour被销毁时调用。
|
|
|
|
|
/// 用于将当前动画器从全局计时器中移除。
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
Clock.RemoveTick(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// ITick接口实现:每帧更新动画状态。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public void Tick()
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果游戏对象不活跃,则不进行更新
|
|
|
|
|
if (!gameObject.activeSelf)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-09-02 11:08:15 +08:00
|
|
|
|
var deltaTime = Time.deltaTime;
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果动画处于暂停状态,则处理暂停逻辑
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_isPaused)
|
|
|
|
|
{
|
|
|
|
|
HandlePausedState();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 否则,播放动画
|
2025-09-02 11:08:15 +08:00
|
|
|
|
PlayAnimation(deltaTime);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 验证并设置动画的初始帧。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected void ValidateStartFrame()
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 确保有精灵序列且不为空以显示有效帧
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_sprites != null && _sprites.Length > 0)
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 确保当前帧索引在有效范围内
|
2025-09-02 11:08:15 +08:00
|
|
|
|
_currentFrameIndex = Mathf.Clamp(_currentFrameIndex, 0, _sprites.Length - 1);
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 调用抽象方法设置当前帧的精灵
|
|
|
|
|
SetDisplaySprite(_sprites[_currentFrameIndex]);
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果没有精灵,则调用抽象方法清空显示组件的精灵
|
|
|
|
|
SetDisplaySprite(null);
|
|
|
|
|
// 如果没有精灵可显示,默认隐藏游戏对象
|
|
|
|
|
gameObject.SetActive(false);
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 处理动画处于暂停状态时的显示逻辑。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected void HandlePausedState()
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果设置了静态精灵,则优先显示静态精灵
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_staticSprite)
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 调用抽象方法显示静态精灵
|
|
|
|
|
SetDisplaySprite(_staticSprite);
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 否则,保持当前显示的精灵(SetDisplaySprite已在NextFrame或ValidateStartFrame中设置),无需额外操作。
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 根据时间增量播放动画。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="deltaTime">自上一帧以来的时间增量。</param>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected void PlayAnimation(float deltaTime)
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果没有精灵序列或序列为空,则清空显示并隐藏游戏对象
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_sprites == null || _sprites.Length == 0)
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 确保显示组件的精灵被清除
|
2025-09-02 11:08:15 +08:00
|
|
|
|
SetDisplaySprite(null);
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 没有精灵可显示,隐藏游戏对象
|
|
|
|
|
gameObject.SetActive(false);
|
2025-09-02 11:08:15 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新帧计时器
|
|
|
|
|
_frameTimer += deltaTime;
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 计算每帧的持续时间
|
2025-09-02 11:08:15 +08:00
|
|
|
|
var frameDuration = 1f / _fps;
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 检查是否满足帧切换条件,如果一帧时间过去,则切换到下一帧
|
2025-09-02 11:08:15 +08:00
|
|
|
|
while (_frameTimer >= frameDuration)
|
|
|
|
|
{
|
|
|
|
|
_frameTimer -= frameDuration;
|
|
|
|
|
NextFrame();
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果是一次性播放模式,并且动画已播放到最后一帧并暂停,则跳出循环,防止在同一帧内进行二次处理
|
|
|
|
|
if (_isOneShot && _currentFrameIndex == _sprites.Length - 1 && _isPaused)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 切换到动画的下一帧。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
protected void NextFrame()
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果没有精灵序列或序列为空,则直接返回
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_sprites == null || _sprites.Length == 0) return;
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 增加帧索引
|
|
|
|
|
_currentFrameIndex++;
|
|
|
|
|
|
|
|
|
|
// 检查是否播放到精灵序列的末尾
|
|
|
|
|
if (_currentFrameIndex >= _sprites.Length)
|
|
|
|
|
{
|
|
|
|
|
// 如果是一次性播放模式
|
|
|
|
|
if (_isOneShot)
|
|
|
|
|
{
|
|
|
|
|
// 播放到最后一帧后停止,并保持在最后一帧
|
|
|
|
|
_currentFrameIndex = _sprites.Length - 1;
|
|
|
|
|
// 确保显示最后一帧
|
|
|
|
|
SetDisplaySprite(_sprites[_currentFrameIndex]);
|
|
|
|
|
// 调用一次性动画结束处理
|
|
|
|
|
FinishOneShotAnimation();
|
|
|
|
|
return; // 停止进一步处理
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 循环播放模式下,重置到第一帧
|
|
|
|
|
_currentFrameIndex = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用抽象方法更新显示组件的精灵
|
|
|
|
|
SetDisplaySprite(_sprites[_currentFrameIndex]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 处理一次性动画播放结束时的逻辑。
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected void FinishOneShotAnimation()
|
|
|
|
|
{
|
|
|
|
|
_isOneShot = false; // 结束一次性播放模式
|
|
|
|
|
_isPaused = true; // 动画暂停
|
|
|
|
|
gameObject.SetActive(false); // 隐藏当前游戏对象
|
|
|
|
|
// TODO: 如果有动画结束回调,可以在这里触发
|
|
|
|
|
// _onOneShotFinished?.Invoke();
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置动画的暂停状态。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="paused">如果为true,动画将暂停;如果为false,动画将继续播放。</param>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public void SetPaused(bool paused) => _isPaused = paused;
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置新的精灵序列用于动画播放。
|
|
|
|
|
/// 设置后动画将从第一帧开始播放,并重置暂停和一次性播放模式。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="newSprites">新的精灵数组。</param>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public void SetSprites(Sprite[] newSprites)
|
|
|
|
|
{
|
|
|
|
|
_sprites = newSprites;
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果有新的精灵数组且不为空,则立即显示第一帧
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_sprites != null && _sprites.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
_currentFrameIndex = 0; // 重置当前帧索引为第一帧
|
|
|
|
|
SetDisplaySprite(_sprites[_currentFrameIndex]); // 立即显示第一帧
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
SetDisplaySprite(null); // 如果没有精灵,则清空显示组件的精灵
|
|
|
|
|
gameObject.SetActive(false); // 如果没有精灵可显示,隐藏游戏对象
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 重置帧计时器,以确保从新精灵序列的开头开始播放
|
2025-09-02 11:08:15 +08:00
|
|
|
|
_frameTimer = 0f;
|
2025-09-06 12:25:55 +08:00
|
|
|
|
_isOneShot = false; // 设置新精灵时,取消一次性播放模式
|
|
|
|
|
_isPaused = false; // 默认开始播放
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 将动画状态恢复到初始设置。
|
|
|
|
|
/// 通常恢复到第一帧并暂停。
|
|
|
|
|
/// </summary>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public void Restore()
|
|
|
|
|
{
|
|
|
|
|
_currentFrameIndex = 0;
|
2025-09-06 12:25:55 +08:00
|
|
|
|
_isOneShot = false; // 重置时取消一次性播放模式
|
|
|
|
|
_isPaused = true; // 默认恢复后暂停
|
|
|
|
|
_frameTimer = 0f;
|
|
|
|
|
|
|
|
|
|
// 如果有精灵序列且不为空,则恢复到第一帧并尝试显示
|
2025-09-02 11:08:15 +08:00
|
|
|
|
if (_sprites != null && _sprites.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
SetDisplaySprite(_sprites[_currentFrameIndex]); // 恢复到第一帧
|
2025-09-06 12:25:55 +08:00
|
|
|
|
// 如果游戏对象当前不活跃,则激活它使其可见
|
|
|
|
|
if (!gameObject.activeSelf)
|
|
|
|
|
{
|
|
|
|
|
gameObject.SetActive(true);
|
|
|
|
|
}
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-09-06 12:25:55 +08:00
|
|
|
|
SetDisplaySprite(null); // 如果没有精灵,则清空显示
|
|
|
|
|
gameObject.SetActive(false); // 隐藏游戏对象
|
2025-09-02 11:08:15 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 以一次性播放模式播放动画。
|
|
|
|
|
/// 动画将从第一帧播放到最后一帧,然后停止并隐藏游戏对象。
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void PlayOneShot()
|
|
|
|
|
{
|
|
|
|
|
// 如果没有精灵序列或序列为空,则发出警告并隐藏游戏对象
|
|
|
|
|
if (_sprites == null || _sprites.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("无法播放一次性动画:未分配精灵。", this);
|
|
|
|
|
gameObject.SetActive(false); // 没有精灵可显示,隐藏游戏对象
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置动画状态为一次性播放模式
|
|
|
|
|
_isOneShot = true;
|
|
|
|
|
_isPaused = false;
|
|
|
|
|
_currentFrameIndex = 0;
|
|
|
|
|
_frameTimer = 0f;
|
|
|
|
|
|
|
|
|
|
// 确保游戏对象处于激活状态才能播放
|
|
|
|
|
if (!gameObject.activeSelf)
|
|
|
|
|
{
|
|
|
|
|
gameObject.SetActive(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 立即显示第一帧
|
|
|
|
|
SetDisplaySprite(_sprites[_currentFrameIndex]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置动画的每秒帧数(FPS)。
|
|
|
|
|
/// FPS值将被限制在最小0.1,以防止除零或过小的帧率。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="newFPS">新的每秒帧数。</param>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public void SetFPS(float newFPS) => _fps = Mathf.Max(0.1f, newFPS);
|
2025-09-06 12:25:55 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置动画暂停时显示的静态精灵。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="sprite">新的静态精灵。</param>
|
2025-09-02 11:08:15 +08:00
|
|
|
|
public void SetStaticSprite(Sprite sprite) => _staticSprite = sprite;
|
|
|
|
|
}
|
|
|
|
|
}
|