Files
Gen_Hack-and-Slash-Roguelit…/Client/Assets/Scripts/Prefab/BaseAnimator.cs

338 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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