using System; using UnityEngine; namespace Base { /// /// 抽象基类,用于封装通用动画逻辑。 /// 所有的动画播放器都应继承自该类,并实现其抽象方法来控制具体的显示组件。 /// public abstract class BaseAnimator : MonoBehaviour, ITick { // 通用公开字段(可在编辑器中设置) /// /// 动画播放的精灵序列。 /// [SerializeField] protected Sprite[] _sprites; /// /// 每秒帧数,控制动画播放速度。 /// [SerializeField] protected float _fps = 2; /// /// 动画暂停时显示的静态精灵。 /// 如果未设置,则默认保持当前帧。 /// [SerializeField] protected Sprite _staticSprite; // 通用内部状态 /// /// 指示动画是否处于暂停状态。 /// protected bool _isPaused; /// /// 用于跟踪当前帧播放时间的计时器。 /// protected float _frameTimer; /// /// 当前正在显示的精灵帧索引。 /// protected int _currentFrameIndex; /// /// 指示动画是否为一次性播放模式。 /// protected bool _isOneShot; /// /// 抽象方法:子类必须实现此方法以获取并验证其特有的显示组件。 /// 例如:SpriteRenderer、Image等。 /// protected abstract void ValidateComponent(); /// /// 抽象方法:子类必须实现此方法以将给定的精灵设置到实际显示组件上。 /// /// 要显示的精灵。 protected abstract void SetDisplaySprite(Sprite sprite); /// /// Unity生命周期方法:在脚本实例被加载时调用。 /// 主要用于初始化组件和动画的第一帧。 /// protected virtual void Awake() { // 子类获取并验证其显示组件 ValidateComponent(); // 初始化动画的第一帧 ValidateStartFrame(); } /// /// Unity生命周期方法:在第一次帧更新之前调用。 /// 用于将当前动画器添加到全局计时器。 /// private void Start() { Clock.AddTick(this); } /// /// Unity生命周期方法:在MonoBehaviour被销毁时调用。 /// 用于将当前动画器从全局计时器中移除。 /// private void OnDestroy() { Clock.RemoveTick(this); } /// /// ITick接口实现:每帧更新动画状态。 /// public void Tick() { // 如果游戏对象不活跃,则不进行更新 if (!gameObject.activeSelf) return; var deltaTime = Time.deltaTime; // 如果动画处于暂停状态,则处理暂停逻辑 if (_isPaused) { HandlePausedState(); return; } // 否则,播放动画 PlayAnimation(deltaTime); } /// /// 验证并设置动画的初始帧。 /// 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); } } /// /// 处理动画处于暂停状态时的显示逻辑。 /// protected void HandlePausedState() { // 如果设置了静态精灵,则优先显示静态精灵 if (_staticSprite) { // 调用抽象方法显示静态精灵 SetDisplaySprite(_staticSprite); } // 否则,保持当前显示的精灵(SetDisplaySprite已在NextFrame或ValidateStartFrame中设置),无需额外操作。 } /// /// 根据时间增量播放动画。 /// /// 自上一帧以来的时间增量。 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; } } } /// /// 切换到动画的下一帧。 /// 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]); } /// /// 处理一次性动画播放结束时的逻辑。 /// protected void FinishOneShotAnimation() { _isOneShot = false; // 结束一次性播放模式 _isPaused = true; // 动画暂停 gameObject.SetActive(false); // 隐藏当前游戏对象 // TODO: 如果有动画结束回调,可以在这里触发 // _onOneShotFinished?.Invoke(); } /// /// 设置动画的暂停状态。 /// /// 如果为true,动画将暂停;如果为false,动画将继续播放。 public void SetPaused(bool paused) => _isPaused = paused; /// /// 设置新的精灵序列用于动画播放。 /// 设置后动画将从第一帧开始播放,并重置暂停和一次性播放模式。 /// /// 新的精灵数组。 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; // 默认开始播放 } /// /// 将动画状态恢复到初始设置。 /// 通常恢复到第一帧并暂停。 /// 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); // 隐藏游戏对象 } } /// /// 以一次性播放模式播放动画。 /// 动画将从第一帧播放到最后一帧,然后停止并隐藏游戏对象。 /// 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]); } /// /// 设置动画的每秒帧数(FPS)。 /// FPS值将被限制在最小0.1,以防止除零或过小的帧率。 /// /// 新的每秒帧数。 public void SetFPS(float newFPS) => _fps = Mathf.Max(0.1f, newFPS); /// /// 设置动画暂停时显示的静态精灵。 /// /// 新的静态精灵。 public void SetStaticSprite(Sprite sprite) => _staticSprite = sprite; } }