Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- добавлена новая система призов: PrizeBase, PrizeType, PrizeDatabase, LevelPrizeConfig и реализации для hopper, QR-кода, отсутствия приза и стойки с призом - добавлена конфигурация призов уровней через `prizes.json` и связанная логика загрузки/обработки в менеджерах игры - расширен UI главного меню и настроек уровней: добавлены LevelSettingsModal, LevelPrizeSettingsModal, QRCodeModal и PrizeStandModal - обновлена навигация модальных окон и связанный UI-код: ModalManager, MainMenu, RulesModal, PauseMenu, PrizeErrorDisplay, UIRowPointer и WinBannerController - доработаны настройки и генерация UI-элементов: обновлены SettingsDatabase, SettingsUIGenerator, SettingUIItem, ValueSelectorRow и editor drawer для настроек - обновлены и добавлены UI-prefab'ы для кнопок, строк настроек, панели групп и кнопки настроек уровня - обновлены сцены `MainMenu` и `Level UI` под новый флоу настройки уровней, выбора призов и отображения модалок - добавлены файлы лаунчера в `StreamingAssets`, включая `RRLauncher.exe`, служебные meta-файлы и документацию - улучшено логирование через FileLogger и удален устаревший код лицензирования - удалены больше неиспользуемые файлы: `Licensing.cs`, `SteppedSlider.cs` и recovery-сцены
276 lines
9.9 KiB
C#
276 lines
9.9 KiB
C#
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
namespace RR.Common.Settings.UI
|
||
{
|
||
/// <summary>
|
||
/// Компонент-Selectable для элемента настроек с выбором значения стрелками влево/вправо.
|
||
/// Поддерживает как int, так и float значения.
|
||
/// При зажатии кнопки скорость изменения плавно увеличивается.
|
||
/// Автоповтор целиком управляется Event System (inputActionsPerSecond).
|
||
/// Работает на одном префабе вместе с SettingUIItem.
|
||
/// </summary>
|
||
public class ValueSelectorRow : Selectable, IMoveHandler
|
||
{
|
||
[Header("References")]
|
||
[SerializeField] private TMP_Text valueText;
|
||
|
||
private SettingEntry _entry;
|
||
private bool _isFloat;
|
||
private int _currentInt;
|
||
private float _currentFloat;
|
||
private int _intStep = 1;
|
||
private float _floatStep = 0.1f;
|
||
|
||
// ───────────────────── Ускорение при зажатии ─────────────────────
|
||
|
||
/// <summary>Максимальный множитель шага при длительном зажатии.</summary>
|
||
private const int MaxStepMultiplier = 10;
|
||
|
||
/// <summary>Сколько вызовов OnMove до начала роста множителя.</summary>
|
||
private const int MultiplierStartCount = 6;
|
||
|
||
/// <summary>Сколько вызовов OnMove до достижения максимального множителя.</summary>
|
||
private const int MultiplierMaxCount = 30;
|
||
|
||
/// <summary>Если между двумя OnMove прошло больше этого времени — сброс множителя.</summary>
|
||
private const float HoldResetTimeout = 0.25f;
|
||
|
||
private int _holdDirection; // -1 = left, +1 = right, 0 = idle
|
||
private int _holdCount; // Счётчик последовательных OnMove в одном направлении
|
||
private float _lastMoveTime; // Time.unscaledTime последнего OnMove
|
||
|
||
// ───────────────────── Int Setup ─────────────────────
|
||
|
||
public void Setup(SettingEntry entry, int currentValue)
|
||
{
|
||
if (entry == null)
|
||
{
|
||
Debug.LogError("[ValueSelectorRow] Cannot setup with null entry!");
|
||
return;
|
||
}
|
||
|
||
_entry = entry;
|
||
_isFloat = false;
|
||
|
||
if (entry.intStep <= 0)
|
||
{
|
||
Debug.LogWarning($"[ValueSelectorRow] Invalid step {entry.intStep} for key {entry.key}, using 1");
|
||
_intStep = 1;
|
||
}
|
||
else
|
||
{
|
||
_intStep = entry.intStep;
|
||
}
|
||
|
||
_currentInt = Mathf.Clamp(currentValue, entry.minInt, entry.maxInt);
|
||
UpdateUI();
|
||
}
|
||
|
||
// ───────────────────── Float Setup ─────────────────────
|
||
|
||
public void Setup(SettingEntry entry, float currentValue)
|
||
{
|
||
if (entry == null)
|
||
{
|
||
Debug.LogError("[ValueSelectorRow] Cannot setup with null entry!");
|
||
return;
|
||
}
|
||
|
||
_entry = entry;
|
||
_isFloat = true;
|
||
|
||
if (entry.floatStep <= 0f)
|
||
{
|
||
Debug.LogWarning($"[ValueSelectorRow] Invalid step {entry.floatStep} for key {entry.key}, using 0.1");
|
||
_floatStep = 0.1f;
|
||
}
|
||
else
|
||
{
|
||
_floatStep = entry.floatStep;
|
||
}
|
||
|
||
_currentFloat = Mathf.Clamp(currentValue, entry.minFloat, entry.maxFloat);
|
||
UpdateUI();
|
||
}
|
||
|
||
// ───────────────────── Navigation ─────────────────────
|
||
|
||
public new void OnMove(AxisEventData eventData)
|
||
{
|
||
if (_entry == null || !IsInteractable())
|
||
{
|
||
base.OnMove(eventData);
|
||
return;
|
||
}
|
||
|
||
switch (eventData.moveDir)
|
||
{
|
||
case MoveDirection.Left:
|
||
ApplyMove(-1);
|
||
eventData.Use();
|
||
break;
|
||
|
||
case MoveDirection.Right:
|
||
ApplyMove(1);
|
||
eventData.Use();
|
||
break;
|
||
|
||
case MoveDirection.Up:
|
||
case MoveDirection.Down:
|
||
ResetHold();
|
||
base.OnMove(eventData);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ───────────────────── Move Logic ─────────────────────
|
||
|
||
private void ApplyMove(int direction)
|
||
{
|
||
float now = Time.unscaledTime;
|
||
|
||
// Если направление сменилось или прошло слишком много времени — сбрасываем
|
||
if (_holdDirection != direction || (now - _lastMoveTime) > HoldResetTimeout)
|
||
{
|
||
_holdDirection = direction;
|
||
_holdCount = 0;
|
||
}
|
||
|
||
_lastMoveTime = now;
|
||
_holdCount++;
|
||
|
||
// Вычисляем множитель шага по количеству последовательных нажатий
|
||
int multiplier = 1;
|
||
if (_holdCount > MultiplierStartCount)
|
||
{
|
||
float progress = Mathf.Clamp01(
|
||
(float)(_holdCount - MultiplierStartCount) / (MultiplierMaxCount - MultiplierStartCount));
|
||
multiplier = Mathf.Max(1, Mathf.RoundToInt(Mathf.Lerp(1f, MaxStepMultiplier, progress)));
|
||
}
|
||
|
||
if (direction > 0)
|
||
IncreaseValue(multiplier);
|
||
else
|
||
DecreaseValue(multiplier);
|
||
}
|
||
|
||
private void ResetHold()
|
||
{
|
||
_holdDirection = 0;
|
||
_holdCount = 0;
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
base.OnDisable();
|
||
ResetHold();
|
||
}
|
||
|
||
public override void OnDeselect(BaseEventData eventData)
|
||
{
|
||
base.OnDeselect(eventData);
|
||
ResetHold();
|
||
}
|
||
|
||
// ───────────────────── Value Changes ─────────────────────
|
||
|
||
private void DecreaseValue(int multiplier)
|
||
{
|
||
if (_isFloat)
|
||
{
|
||
float newValue = Mathf.Max(_entry.minFloat, _currentFloat - _floatStep * multiplier);
|
||
if (!Mathf.Approximately(newValue, _currentFloat))
|
||
SetValue(newValue);
|
||
}
|
||
else
|
||
{
|
||
int newValue = Mathf.Max(_entry.minInt, _currentInt - _intStep * multiplier);
|
||
if (newValue != _currentInt)
|
||
SetValue(newValue);
|
||
}
|
||
}
|
||
|
||
private void IncreaseValue(int multiplier)
|
||
{
|
||
if (_isFloat)
|
||
{
|
||
float newValue = Mathf.Min(_entry.maxFloat, _currentFloat + _floatStep * multiplier);
|
||
if (!Mathf.Approximately(newValue, _currentFloat))
|
||
SetValue(newValue);
|
||
}
|
||
else
|
||
{
|
||
int newValue = Mathf.Min(_entry.maxInt, _currentInt + _intStep * multiplier);
|
||
if (newValue != _currentInt)
|
||
SetValue(newValue);
|
||
}
|
||
}
|
||
|
||
// ───────────────────── UI ─────────────────────
|
||
|
||
private void UpdateUI()
|
||
{
|
||
if (valueText == null) return;
|
||
|
||
if (_isFloat)
|
||
{
|
||
if (_entry != null && _entry.displayAsPercentage)
|
||
valueText.text = (_currentFloat * 100f).ToString("0") + "%";
|
||
else
|
||
valueText.text = _currentFloat.ToString("0.##");
|
||
}
|
||
else
|
||
{
|
||
valueText.text = _currentInt.ToString();
|
||
}
|
||
}
|
||
|
||
// ───────────────────── Public API (int) ─────────────────────
|
||
|
||
public void SetValue(int value)
|
||
{
|
||
if (_entry == null) return;
|
||
|
||
int clamped = Mathf.Clamp(value, _entry.minInt, _entry.maxInt);
|
||
if (clamped != _currentInt)
|
||
{
|
||
_currentInt = clamped;
|
||
UpdateUI();
|
||
|
||
var manager = SettingsManager.Instance;
|
||
if (manager != null)
|
||
manager.SetInt(_entry.key, _currentInt);
|
||
else
|
||
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
|
||
}
|
||
}
|
||
|
||
// ───────────────────── Public API (float) ─────────────────────
|
||
|
||
public void SetValue(float value)
|
||
{
|
||
if (_entry == null) return;
|
||
|
||
float clamped = Mathf.Clamp(value, _entry.minFloat, _entry.maxFloat);
|
||
if (!Mathf.Approximately(clamped, _currentFloat))
|
||
{
|
||
_currentFloat = clamped;
|
||
UpdateUI();
|
||
|
||
var manager = SettingsManager.Instance;
|
||
if (manager != null)
|
||
manager.SetFloat(_entry.key, _currentFloat);
|
||
else
|
||
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
|
||
}
|
||
}
|
||
|
||
public bool IsFloat => _isFloat;
|
||
public string Key => _entry?.key;
|
||
}
|
||
}
|
||
|