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-сцены
207 lines
8.1 KiB
C#
207 lines
8.1 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.InputSystem;
|
||
using UnityEngine.UI;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Модальное окно с правилами игры.
|
||
/// Поддерживает несколько страниц с переключением физическими кнопками влево/вправо.
|
||
/// </summary>
|
||
public class RulesModal : Modal
|
||
{
|
||
// ───────────────────────── Страницы ─────────────────────────
|
||
|
||
[Header("Страницы правил")]
|
||
[Tooltip("Список страниц правил. Каждая страница — это отдельный GameObject, " +
|
||
"который будет показываться/скрываться при переключении.")]
|
||
[SerializeField] private List<GameObject> pages = new();
|
||
|
||
// ───────────────────────── Навигация ─────────────────────────
|
||
|
||
[Header("Навигация (Input System)")]
|
||
[Tooltip("InputAction навигации (Navigate, тип Value/Vector2)")]
|
||
[SerializeField] private InputActionReference navigateAction;
|
||
|
||
[Tooltip("Порог срабатывания по оси X (0..1)")]
|
||
[SerializeField] private float deadZone = 0.5f;
|
||
|
||
[Tooltip("Зацикливать навигацию (с последней страницы на первую и наоборот)")]
|
||
[SerializeField] private bool loop;
|
||
|
||
|
||
// ───────────────────────── Индикатор ─────────────────────────
|
||
|
||
[Header("Индикатор страниц (опционально)")]
|
||
[Tooltip("Текст с номером страницы (например '2 / 5'). Может быть null.")]
|
||
[SerializeField] private TMP_Text pageIndicatorText;
|
||
|
||
[Tooltip("Формат текста индикатора. {0} — текущая, {1} — всего.")]
|
||
[SerializeField] private string pageIndicatorFormat = "{0} / {1}";
|
||
|
||
[Tooltip("Контейнер с точками-индикаторами. Дочерние объекты = точки. Может быть null.")]
|
||
[SerializeField] private Transform dotsContainer;
|
||
|
||
[Tooltip("Цвет активной точки")]
|
||
[SerializeField] private Color dotActiveColor = Color.white;
|
||
|
||
[Tooltip("Цвет неактивной точки")]
|
||
[SerializeField] private Color dotInactiveColor = new Color(1f, 1f, 1f, 0.35f);
|
||
|
||
[Header("Стрелки навигации (опционально)")]
|
||
[Tooltip("Изображение стрелки 'назад'. Скрывается, если предыдущей страницы нет.")]
|
||
[SerializeField] private RawImage prevArrowImage;
|
||
|
||
[Tooltip("Изображение стрелки 'вперёд'. Скрывается, если следующей страницы нет.")]
|
||
[SerializeField] private RawImage nextArrowImage;
|
||
|
||
// ───────────────────────── Состояние ─────────────────────────
|
||
|
||
private int _currentPage;
|
||
private bool _axisHeld;
|
||
|
||
/// <summary>Текущий индекс страницы (0-based).</summary>
|
||
public int CurrentPage => _currentPage;
|
||
|
||
/// <summary>Общее количество страниц.</summary>
|
||
public int PageCount => pages.Count;
|
||
|
||
/// <summary>Вызывается при смене страницы. Параметр — новый индекс.</summary>
|
||
public event Action<int> OnPageChanged;
|
||
|
||
// ───────────────────────── Lifecycle ─────────────────────────
|
||
|
||
protected override void OnEnable()
|
||
{
|
||
base.OnEnable();
|
||
ShowPage(0);
|
||
|
||
if (navigateAction != null && navigateAction.action != null)
|
||
{
|
||
navigateAction.action.Enable();
|
||
navigateAction.action.performed += OnNavigatePerformed;
|
||
navigateAction.action.canceled += OnNavigateCanceled;
|
||
}
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
if (navigateAction != null && navigateAction.action != null)
|
||
{
|
||
navigateAction.action.performed -= OnNavigatePerformed;
|
||
navigateAction.action.canceled -= OnNavigateCanceled;
|
||
}
|
||
|
||
_axisHeld = false;
|
||
base.OnDisable();
|
||
}
|
||
|
||
// ───────────────────────── Ввод (Input System) ─────────────────────────
|
||
|
||
private void OnNavigatePerformed(InputAction.CallbackContext ctx)
|
||
{
|
||
float x = ctx.ReadValue<Vector2>().x;
|
||
|
||
if (Mathf.Abs(x) < deadZone)
|
||
{
|
||
_axisHeld = false;
|
||
return;
|
||
}
|
||
|
||
if (_axisHeld) return;
|
||
_axisHeld = true;
|
||
|
||
if (x < 0f)
|
||
PrevPage();
|
||
else
|
||
NextPage();
|
||
}
|
||
|
||
private void OnNavigateCanceled(InputAction.CallbackContext ctx)
|
||
{
|
||
_axisHeld = false;
|
||
}
|
||
|
||
|
||
// ───────────────────────── Навигация ─────────────────────────
|
||
|
||
/// <summary>Перейти на предыдущую страницу.</summary>
|
||
public void PrevPage()
|
||
{
|
||
if (pages.Count == 0) return;
|
||
|
||
int target = _currentPage - 1;
|
||
|
||
if (target < 0)
|
||
target = loop ? pages.Count - 1 : 0;
|
||
|
||
ShowPage(target);
|
||
}
|
||
|
||
/// <summary>Перейти на следующую страницу.</summary>
|
||
public void NextPage()
|
||
{
|
||
if (pages.Count == 0) return;
|
||
|
||
int target = _currentPage + 1;
|
||
|
||
if (target >= pages.Count)
|
||
target = loop ? 0 : pages.Count - 1;
|
||
|
||
ShowPage(target);
|
||
}
|
||
|
||
/// <summary>Показать страницу по индексу.</summary>
|
||
public void ShowPage(int index)
|
||
{
|
||
if (pages.Count == 0) return;
|
||
|
||
index = Mathf.Clamp(index, 0, pages.Count - 1);
|
||
|
||
for (int i = 0; i < pages.Count; i++)
|
||
{
|
||
if (pages[i] != null)
|
||
pages[i].SetActive(i == index);
|
||
}
|
||
|
||
_currentPage = index;
|
||
|
||
UpdateIndicator();
|
||
OnPageChanged?.Invoke(_currentPage);
|
||
}
|
||
|
||
// ───────────────────────── UI Update ─────────────────────────
|
||
|
||
private void UpdateIndicator()
|
||
{
|
||
if (pageIndicatorText != null)
|
||
{
|
||
pageIndicatorText.text = string.Format(pageIndicatorFormat, _currentPage + 1, pages.Count);
|
||
}
|
||
|
||
if (dotsContainer != null)
|
||
{
|
||
for (int i = 0; i < dotsContainer.childCount; i++)
|
||
{
|
||
var graphic = dotsContainer.GetChild(i).GetComponent<Graphic>();
|
||
if (graphic != null)
|
||
graphic.color = i == _currentPage ? dotActiveColor : dotInactiveColor;
|
||
}
|
||
}
|
||
|
||
if (prevArrowImage != null)
|
||
{
|
||
prevArrowImage.gameObject.SetActive(loop || _currentPage > 0);
|
||
}
|
||
|
||
if (nextArrowImage != null)
|
||
{
|
||
nextArrowImage.gameObject.SetActive(loop || _currentPage < pages.Count - 1);
|
||
}
|
||
}
|
||
}
|
||
}
|