177 lines
6.5 KiB
C#
177 lines
6.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using DG.Tweening;
|
||
using SLSFramework.General;
|
||
using UnityEngine;
|
||
|
||
namespace Continentis.MainGame.Character
|
||
{
|
||
/// <summary>
|
||
/// Tier 1 动画驱动器:静态图片切换 + DOTween 位移。
|
||
/// 不使用 AnimationClip,通过切换 SpriteRenderer.sprite 表现不同动作。
|
||
/// 适合尚无帧动画资源的角色,或仅有立绘/静态图片的简易角色。
|
||
/// </summary>
|
||
public class StaticSpriteAnimator : MonoBehaviour, ICharacterAnimator
|
||
{
|
||
[Header("Sprite 映射")]
|
||
[Tooltip("动作名称 → Sprite 映射表,Key 需与 CharacterData.animations 的 Key 对齐(如 Idle、Attack、Hit)")]
|
||
public SerializableDictionary<string, Sprite> 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 ──────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 初始化:缓存主 SpriteRenderer 和 Idle Sprite。
|
||
/// </summary>
|
||
public void InitializeAnimator(CombatCharacterViewBase view)
|
||
{
|
||
_mainSpriteRenderer = view.mainView.GetComponent<SpriteRenderer>();
|
||
if (_mainSpriteRenderer == null)
|
||
_mainSpriteRenderer = view.mainView.GetComponentInChildren<SpriteRenderer>();
|
||
|
||
_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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 播放动作:切换 Sprite + DOTween 动效,完毕后自动回 Idle。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 立即切回 Idle Sprite 并复位位置。
|
||
/// </summary>
|
||
public void ReturnToIdle()
|
||
{
|
||
KillCurrentTween();
|
||
|
||
if (_mainSpriteRenderer != null && _idleSprite != null)
|
||
_mainSpriteRenderer.sprite = _idleSprite;
|
||
|
||
if (_mainSpriteRenderer != null)
|
||
_mainSpriteRenderer.transform.localPosition = _originalLocalPosition;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 暂停或恢复当前 Tween。
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
}
|