127 lines
4.1 KiB
C#

using System;
using TMPro;
using UnityEngine;
public class MNumberStepper : MonoBehaviour
{
public event Action<int> OnValueChanged;
[SerializeField] private TextMeshProUGUI _valueLabel;
[SerializeField] private CanvasGroup _leftArrow;
[SerializeField] private CanvasGroup _rightArrow;
[Header("Настройки ускорения")]
[SerializeField] private float _accelerationTime = 0.8f; // Время до максимальной скорости
[SerializeField] private int _maxStep = 10; // Максимальный шаг
[SerializeField] private float _minInterval = 0.5f; // Начальный интервал (сек)
[SerializeField] private float _maxInterval = 0.03f; // Минимальный интервал (сек)
[SerializeField] private AnimationCurve _accelerationCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
private float _holdTime;
private int _lastDirection;
private bool _isHolding;
private float _lastChangeTime;
public int Value { get; private set; }
private int _min;
private int _max;
public void Initialize(int value, int min, int max)
{
Value = value;
_min = min;
_max = max;
ResetState();
UpdateView();
}
public void SetDirection(int direction)
{
if (direction == 0)
{
// Отпустили кнопку
_isHolding = false;
_lastDirection = 0;
_holdTime = 0f;
return;
}
if (!_isHolding)
{
// Первое нажатие - мгновенно меняем значение
_isHolding = true;
_lastDirection = direction;
_holdTime = 0f;
_lastChangeTime = Time.unscaledTime;
// Мгновенное изменение при первом клике
ChangeValue(direction, 1);
return;
}
// Если направление изменилось - сбрасываем ускорение
if (_lastDirection != direction)
{
_lastDirection = direction;
_holdTime = 0f;
_lastChangeTime = Time.unscaledTime;
return;
}
// Увеличиваем время удержания
_holdTime += Time.unscaledDeltaTime;
// Вычисляем прогресс ускорения (0 -> 1)
float progress = Mathf.Clamp01(_holdTime / _accelerationTime);
float curveValue = _accelerationCurve.Evaluate(progress);
// 1. РАССЧИТЫВАЕМ ШАГ (увеличивается от 1 до _maxStep)
int step = 1 + Mathf.RoundToInt(curveValue * (_maxStep - 1));
step = Mathf.Max(1, step); // Шаг не может быть меньше 1
// 2. РАССЧИТЫВАЕМ ИНТЕРВАЛ (уменьшается от _minInterval до _maxInterval)
float currentInterval = Mathf.Lerp(_minInterval, _maxInterval, curveValue);
// Проверяем, пора ли менять значение
float timeSinceLastChange = Time.unscaledTime - _lastChangeTime;
if (timeSinceLastChange >= currentInterval)
{
_lastChangeTime = Time.unscaledTime;
ChangeValue(_lastDirection, step);
}
}
private void ChangeValue(int direction, int step)
{
int newValue = Value + direction * step;
newValue = Mathf.Clamp(newValue, _min, _max);
if (newValue != Value)
{
Value = newValue;
OnValueChanged?.Invoke(Value);
UpdateView();
}
else
{
// Если уперлись в границу - сбрасываем ускорение
ResetState();
}
}
private void UpdateView()
{
_valueLabel.text = Value.ToString();
_leftArrow.alpha = Value == _min ? 0 : 1;
_rightArrow.alpha = Value == _max ? 0 : 1;
}
public void ResetState()
{
_isHolding = false;
_lastDirection = 0;
_holdTime = 0f;
_lastChangeTime = 0f;
}
}