using System; using UnityEngine; namespace RR.Common.UI { /// /// Универсальный аниматор UI-элементов. /// Поддерживает покачивание (rotation), вертикальное движение (bob) и пульсацию масштаба (pulse). /// Каждый эффект включается/отключается и настраивается независимо. /// [AddComponentMenu("RR/UI/UI Element Animator")] public class UIElementAnimator : MonoBehaviour { // ───────────────────────── Enums ───────────────────────── /// Функция плавности для анимации. public enum EaseType { Sine, Smooth, // SmoothStep Linear, Bounce } // ───────────────────────── Swing (покачивание) ───────────────────────── [Serializable] public sealed class SwingSettings { [Tooltip("Включить покачивание")] public bool enabled = true; [Tooltip("Максимальный угол отклонения (градусы)")] [Range(0f, 90f)] public float amplitude = 15f; [Tooltip("Длительность одного полного цикла (туда‑обратно), сек")] [Min(0.01f)] public float duration = 1f; [Tooltip("Сдвиг фазы (0–1)")] [Range(0f, 1f)] public float phaseOffset; [Tooltip("Функция плавности")] public EaseType ease = EaseType.Sine; } // ───────────────────────── Bob (движение вверх‑вниз) ───────────────────────── [Serializable] public sealed class BobSettings { [Tooltip("Включить движение вверх‑вниз")] public bool enabled = true; [Tooltip("Амплитуда движения (пикселей / юнитов)")] [Min(0f)] public float amplitude = 10f; [Tooltip("Длительность одного полного цикла (вверх‑вниз), сек")] [Min(0.01f)] public float duration = 1.2f; [Tooltip("Сдвиг фазы (0–1)")] [Range(0f, 1f)] public float phaseOffset; [Tooltip("Функция плавности")] public EaseType ease = EaseType.Sine; } // ───────────────────────── Pulse (пульсация масштаба) ───────────────────────── [Serializable] public sealed class PulseSettings { [Tooltip("Включить пульсацию масштаба")] public bool enabled = true; [Tooltip("Минимальный множитель масштаба")] [Min(0f)] public float minScale = 0.9f; [Tooltip("Максимальный множитель масштаба")] [Min(0f)] public float maxScale = 1.1f; [Tooltip("Длительность одного полного цикла (увеличение‑уменьшение), сек")] [Min(0.01f)] public float duration = 1f; [Tooltip("Сдвиг фазы (0–1)")] [Range(0f, 1f)] public float phaseOffset; [Tooltip("Функция плавности")] public EaseType ease = EaseType.Smooth; [Tooltip("Пульсировать равномерно по обеим осям")] public bool uniform = true; } // ───────────────────────── Inspector fields ───────────────────────── [Header("Покачивание (Rotation Z)")] [SerializeField] private SwingSettings swing = new(); [Header("Движение вверх‑вниз")] [SerializeField] private BobSettings bob = new(); [Header("Пульсация масштаба")] [SerializeField] private PulseSettings pulse = new(); [Header("Общие")] [Tooltip("Использовать unscaledTime (работает на паузе)")] [SerializeField] private bool useUnscaledTime = true; // ───────────────────────── Runtime state ───────────────────────── private RectTransform _rect; private Transform _transform; private Vector3 _initialLocalPosition; private Quaternion _initialLocalRotation; private Vector3 _initialLocalScale; // ───────────────────────── Public API ───────────────────────── public SwingSettings Swing => swing; public BobSettings Bob => bob; public PulseSettings Pulse => pulse; /// Сбросить все анимации к исходному состоянию. public void ResetToInitial() { Transform t = GetTransform(); t.localPosition = _initialLocalPosition; t.localRotation = _initialLocalRotation; t.localScale = _initialLocalScale; } /// Запомнить текущее состояние как начальное. public void CaptureInitialState() { Transform t = GetTransform(); _initialLocalPosition = t.localPosition; _initialLocalRotation = t.localRotation; _initialLocalScale = t.localScale; } // ───────────────────────── Unity callbacks ───────────────────────── private void Awake() { CaptureInitialState(); } private void Update() { float time = useUnscaledTime ? Time.unscaledTime : Time.time; Transform t = GetTransform(); // --- Swing --- if (swing.enabled) { float phase = time / swing.duration + swing.phaseOffset; float wave = EvaluateWave(phase, swing.ease); float angle = wave * swing.amplitude; t.localRotation = _initialLocalRotation * Quaternion.Euler(0f, 0f, angle); } else { t.localRotation = _initialLocalRotation; } // --- Bob --- if (bob.enabled) { float phase = time / bob.duration + bob.phaseOffset; float wave = EvaluateWave(phase, bob.ease); float offset = wave * bob.amplitude; Vector3 pos = _initialLocalPosition; pos.y += offset; t.localPosition = pos; } else { t.localPosition = _initialLocalPosition; } // --- Pulse --- if (pulse.enabled) { float phase = time / pulse.duration + pulse.phaseOffset; float wave = EvaluateWave01(phase, pulse.ease); // 0..1 float scale = Mathf.Lerp(pulse.minScale, pulse.maxScale, wave); if (pulse.uniform) { t.localScale = _initialLocalScale * scale; } else { t.localScale = new Vector3( _initialLocalScale.x * scale, _initialLocalScale.y * scale, _initialLocalScale.z); } } else { t.localScale = _initialLocalScale; } } // ───────────────────────── Helpers ───────────────────────── private Transform GetTransform() { if (_transform == null) { _rect = GetComponent(); _transform = _rect != null ? _rect : transform; } return _transform; } /// /// Возвращает значение от −1 до 1 (симметричная волна). /// Используется для swing и bob. /// private static float EvaluateWave(float phase, EaseType ease) { float t = Mathf.Repeat(phase, 1f); // 0..1 return ease switch { EaseType.Sine => Mathf.Sin(t * Mathf.PI * 2f), EaseType.Linear => 1f - 4f * Mathf.Abs(t - 0.5f), // triangle wave −1..1 EaseType.Smooth => SmoothTriangle(t), EaseType.Bounce => Mathf.Sin(t * Mathf.PI * 2f) * BounceDecay(t), _ => Mathf.Sin(t * Mathf.PI * 2f) }; } /// /// Возвращает значение от 0 до 1 (однополярная волна). /// Используется для pulse. /// private static float EvaluateWave01(float phase, EaseType ease) { return (EvaluateWave(phase, ease) + 1f) * 0.5f; } /// SmoothStep‑версия треугольной волны (−1..1). private static float SmoothTriangle(float t) { // Треугольная волна 0..1..0 float tri = 1f - 2f * Mathf.Abs(t - 0.5f); // 0..1 tri = tri * tri * (3f - 2f * tri); // SmoothStep return tri * 2f - 1f; // remap to −1..1 } /// Затухание для bounce‑волны. private static float BounceDecay(float t) { // Полный цикл: сильный толчок → затухание → возврат float bounce = 1f - Mathf.Abs(2f * t - 1f); return bounce * bounce; } } }