Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Добавлены новые UI-компоненты и исправлена надёжность подсветки - Добавлен UIElementAnimator — универсальный аниматор UI-элементов (покачивание, движение вверх-вниз, пульсация масштаба) - Добавлен GamePriceDisplay — отображение стоимости игры из настроек (payment.startGameCost, payment.currency) - Добавлен метод StartIdleEffects() в GameManager для гарантированного запуска мигания шапки и последовательности эффектов - Исправлена подсветка, не включавшаяся при переходе в главное меню: OnSceneLoaded теперь вызывает StartIdleEffects() - Исправлено восстановление подсветки при переподключении платы: ComSystemManager использует StartIdleEffects() для MainMenu - Добавлены LightController.IsHatOn и ResendHatState() для корректного отслеживания состояния шапки - Исправлено зависание спецэффектов при мигании шапки: LightSequence теперь переотправляет команду эффекта каждые 2 сек - Убраны дублирующиеся вызовы Blink/SetLight из LoadScene (теперь обрабатывается в OnSceneLoaded) - Обновлена сцена MainMenu, ассеты шрифтов, удалён старый префаб Coin
278 lines
11 KiB
C#
278 lines
11 KiB
C#
using System;
|
||
using UnityEngine;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Универсальный аниматор UI-элементов.
|
||
/// Поддерживает покачивание (rotation), вертикальное движение (bob) и пульсацию масштаба (pulse).
|
||
/// Каждый эффект включается/отключается и настраивается независимо.
|
||
/// </summary>
|
||
[AddComponentMenu("RR/UI/UI Element Animator")]
|
||
public class UIElementAnimator : MonoBehaviour
|
||
{
|
||
// ───────────────────────── Enums ─────────────────────────
|
||
|
||
/// <summary>Функция плавности для анимации.</summary>
|
||
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;
|
||
|
||
/// <summary>Сбросить все анимации к исходному состоянию.</summary>
|
||
public void ResetToInitial()
|
||
{
|
||
Transform t = GetTransform();
|
||
t.localPosition = _initialLocalPosition;
|
||
t.localRotation = _initialLocalRotation;
|
||
t.localScale = _initialLocalScale;
|
||
}
|
||
|
||
/// <summary>Запомнить текущее состояние как начальное.</summary>
|
||
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<RectTransform>();
|
||
_transform = _rect != null ? _rect : transform;
|
||
}
|
||
return _transform;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возвращает значение от −1 до 1 (симметричная волна).
|
||
/// Используется для swing и bob.
|
||
/// </summary>
|
||
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)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возвращает значение от 0 до 1 (однополярная волна).
|
||
/// Используется для pulse.
|
||
/// </summary>
|
||
private static float EvaluateWave01(float phase, EaseType ease)
|
||
{
|
||
return (EvaluateWave(phase, ease) + 1f) * 0.5f;
|
||
}
|
||
|
||
/// <summary>SmoothStep‑версия треугольной волны (−1..1).</summary>
|
||
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
|
||
}
|
||
|
||
/// <summary>Затухание для bounce‑волны.</summary>
|
||
private static float BounceDecay(float t)
|
||
{
|
||
// Полный цикл: сильный толчок → затухание → возврат
|
||
float bounce = 1f - Mathf.Abs(2f * t - 1f);
|
||
return bounce * bounce;
|
||
}
|
||
}
|
||
}
|
||
|