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;
}
}
}