using System; using System.Collections.Generic; using DG.Tweening; using SLSFramework.General; using UnityEngine; namespace Continentis.MainGame.Character { /// /// Tier 1 动画驱动器:静态图片切换 + DOTween 位移。 /// 不使用 AnimationClip,通过切换 SpriteRenderer.sprite 表现不同动作。 /// 适合尚无帧动画资源的角色,或仅有立绘/静态图片的简易角色。 /// public class StaticSpriteAnimator : MonoBehaviour, ICharacterAnimator { [Header("Sprite 映射")] [Tooltip("动作名称 → Sprite 映射表,Key 需与 CharacterData.animations 的 Key 对齐(如 Idle、Attack、Hit)")] public SerializableDictionary actionSprites; [Header("DOTween 参数")] [Tooltip("攻击时前冲距离")] [SerializeField] private float attackLungeDistance = 0.5f; [Tooltip("攻击前冲时长")] [SerializeField] private float attackLungeDuration = 0.15f; [Tooltip("受击抖动强度")] [SerializeField] private float hitShakeStrength = 0.3f; [Tooltip("受击抖动时长")] [SerializeField] private float hitShakeDuration = 0.2f; private SpriteRenderer _mainSpriteRenderer; private Sprite _idleSprite; private Vector3 _originalLocalPosition; private Tween _currentTween; // ── ICharacterAnimator ────────────────────────────────────────────── /// /// 初始化:缓存主 SpriteRenderer 和 Idle Sprite。 /// public void InitializeAnimator(CombatCharacterViewBase view) { _mainSpriteRenderer = view.mainView.GetComponent(); if (_mainSpriteRenderer == null) _mainSpriteRenderer = view.mainView.GetComponentInChildren(); _originalLocalPosition = view.mainView.transform.localPosition; // 尝试从映射表获取 Idle sprite,回退为当前显示的 sprite _idleSprite = actionSprites != null && actionSprites.TryGetValue("Idle", out Sprite idle) ? idle : _mainSpriteRenderer != null ? _mainSpriteRenderer.sprite : null; // 应用 Idle Sprite if (_mainSpriteRenderer != null && _idleSprite != null) _mainSpriteRenderer.sprite = _idleSprite; } /// /// 播放动作:切换 Sprite + DOTween 动效,完毕后自动回 Idle。 /// public void PlayAction(string actionName, float speed = 1f, Action onComplete = null) { KillCurrentTween(); // 切换 Sprite if (_mainSpriteRenderer != null && actionSprites != null && actionSprites.TryGetValue(actionName, out Sprite sprite)) { _mainSpriteRenderer.sprite = sprite; } // 根据动作类型执行 DOTween 动效 float adjustedDuration = 1f / Mathf.Max(speed, 0.01f); Transform mainTransform = _mainSpriteRenderer != null ? _mainSpriteRenderer.transform : transform; switch (actionName) { case "Attack": case "Skill": PlayLungeAnimation(mainTransform, adjustedDuration, onComplete); break; case "Hit": PlayShakeAnimation(mainTransform, adjustedDuration, onComplete); break; default: // 无特殊动效,短暂延迟后回 Idle float holdDuration = DefaultActionHoldDuration * adjustedDuration; _currentTween = DOVirtual.DelayedCall(holdDuration, () => { ReturnToIdle(); onComplete?.Invoke(); }); break; } } /// /// 立即切回 Idle Sprite 并复位位置。 /// public void ReturnToIdle() { KillCurrentTween(); if (_mainSpriteRenderer != null && _idleSprite != null) _mainSpriteRenderer.sprite = _idleSprite; if (_mainSpriteRenderer != null) _mainSpriteRenderer.transform.localPosition = _originalLocalPosition; } /// /// 暂停或恢复当前 Tween。 /// public void SetPause(bool isPaused) { if (_currentTween != null && _currentTween.IsActive()) { if (isPaused) _currentTween.Pause(); else _currentTween.Play(); } } // ── 内部动效 ──────────────────────────────────────────────────────── private const float DefaultActionHoldDuration = 0.3f; private void PlayLungeAnimation(Transform target, float durationScale, Action onComplete) { float duration = attackLungeDuration * durationScale; Vector3 lungeOffset = target.right * attackLungeDistance; _currentTween = DOTween.Sequence() .Append(target.DOLocalMove(_originalLocalPosition + lungeOffset, duration).SetEase(Ease.OutQuad)) .Append(target.DOLocalMove(_originalLocalPosition, duration).SetEase(Ease.InQuad)) .OnComplete(() => { ReturnToIdle(); onComplete?.Invoke(); }); } private void PlayShakeAnimation(Transform target, float durationScale, Action onComplete) { float duration = hitShakeDuration * durationScale; _currentTween = target.DOShakePosition(duration, hitShakeStrength, vibrato: 10, randomness: 90, fadeOut: true) .OnComplete(() => { target.localPosition = _originalLocalPosition; ReturnToIdle(); onComplete?.Invoke(); }); } private void KillCurrentTween() { if (_currentTween != null && _currentTween.IsActive()) { _currentTween.Kill(); _currentTween = null; } } private void OnDestroy() { KillCurrentTween(); } } }