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-сцены
166 lines
5.8 KiB
C#
166 lines
5.8 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.InputSystem;
|
||
using UnityEngine.UI;
|
||
|
||
using RR.Game2D.Core.Managers;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Модальное окно витрины с выбором ячейки/полки для выдачи приза.
|
||
/// Отображает сетку кнопок-ячеек. Навигация — через Input System (Navigate).
|
||
/// </summary>
|
||
public class PrizeStandModal : Modal
|
||
{
|
||
/// <summary>Текущий экземпляр на сцене. Регистрируется автоматически.</summary>
|
||
public static PrizeStandModal Current { get; private set; }
|
||
|
||
[Header("Контент")]
|
||
[Tooltip("Контейнер для кнопок ячеек (например, GridLayoutGroup).")]
|
||
[SerializeField] private Transform boxButtonsContainer;
|
||
|
||
[Tooltip("Префаб кнопки ячейки. Должен содержать Button и TMP_Text.")]
|
||
[SerializeField] private GameObject boxButtonPrefab;
|
||
|
||
[Header("UI")]
|
||
[Tooltip("Текст-заголовок окна (опционально).")]
|
||
[SerializeField] private TMP_Text titleText;
|
||
|
||
[Tooltip("Текст заголовка по умолчанию.")]
|
||
[SerializeField] private string defaultTitle = "Выберите ячейку";
|
||
|
||
[Header("Навигация")]
|
||
[Tooltip("InputAction для навигации (Navigate из Input System).")]
|
||
[SerializeField] private InputActionReference navigateAction;
|
||
|
||
/// <summary>
|
||
/// Вызывается при выборе ячейки. Параметр — номер ячейки (1-based).
|
||
/// Если игрок закрыл окно без выбора, вызывается с 0.
|
||
/// </summary>
|
||
public event Action<int> OnBoxSelected;
|
||
|
||
private readonly List<GameObject> _createdButtons = new();
|
||
private bool _selectionMade;
|
||
|
||
// ────────────── Lifecycle ──────────────
|
||
|
||
protected override void Awake()
|
||
{
|
||
Current = this;
|
||
base.Awake();
|
||
|
||
// При закрытии без выбора — уведомляем с 0
|
||
OnClosed += HandleClosedWithoutSelection;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (Current == this) Current = null;
|
||
OnClosed -= HandleClosedWithoutSelection;
|
||
}
|
||
|
||
// ────────────── Публичный API ──────────────
|
||
|
||
/// <summary>
|
||
/// Показывает окно с указанным количеством ячеек.
|
||
/// </summary>
|
||
/// <param name="boxCount">Количество ячеек для отображения.</param>
|
||
public void Show(int boxCount)
|
||
{
|
||
_selectionMade = false;
|
||
|
||
BuildButtons(boxCount);
|
||
|
||
if (titleText != null)
|
||
titleText.text = defaultTitle;
|
||
|
||
Open();
|
||
GameManager.Instance?.SetPause(true);
|
||
}
|
||
|
||
// ────────────── Внутреннее ──────────────
|
||
|
||
private void BuildButtons(int count)
|
||
{
|
||
ClearButtons();
|
||
|
||
if (boxButtonsContainer == null || boxButtonPrefab == null)
|
||
{
|
||
Debug.LogError("[PrizeStandModal] Контейнер или префаб кнопки не назначены.");
|
||
return;
|
||
}
|
||
|
||
for (int i = 1; i <= count; i++)
|
||
{
|
||
GameObject btnObj = Instantiate(boxButtonPrefab, boxButtonsContainer);
|
||
btnObj.name = $"BoxButton_{i}";
|
||
|
||
// Настраиваем текст
|
||
TMP_Text label = btnObj.GetComponentInChildren<TMP_Text>();
|
||
if (label != null)
|
||
label.text = i.ToString();
|
||
|
||
// Настраиваем кнопку
|
||
Button btn = btnObj.GetComponent<Button>();
|
||
if (btn != null)
|
||
{
|
||
int boxNumber = i; // локальная копия для замыкания
|
||
btn.onClick.AddListener(() => OnBoxButtonClicked(boxNumber));
|
||
}
|
||
|
||
_createdButtons.Add(btnObj);
|
||
}
|
||
|
||
// Устанавливаем первый элемент для фокуса
|
||
if (_createdButtons.Count > 0)
|
||
{
|
||
Button firstBtn = _createdButtons[0].GetComponent<Button>();
|
||
if (firstBtn != null)
|
||
SetFirstSelected(firstBtn);
|
||
}
|
||
}
|
||
|
||
private void OnBoxButtonClicked(int boxNumber)
|
||
{
|
||
_selectionMade = true;
|
||
|
||
Debug.Log($"[PrizeStandModal] Выбрана ячейка #{boxNumber}.");
|
||
|
||
Close();
|
||
GameManager.Instance?.SetPause(false);
|
||
|
||
OnBoxSelected?.Invoke(boxNumber);
|
||
}
|
||
|
||
private void HandleClosedWithoutSelection()
|
||
{
|
||
if (!_selectionMade)
|
||
{
|
||
Debug.Log("[PrizeStandModal] Окно закрыто без выбора.");
|
||
GameManager.Instance?.SetPause(false);
|
||
OnBoxSelected?.Invoke(0);
|
||
}
|
||
}
|
||
|
||
private void ClearButtons()
|
||
{
|
||
foreach (GameObject obj in _createdButtons)
|
||
{
|
||
if (obj != null) Destroy(obj);
|
||
}
|
||
_createdButtons.Clear();
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
base.OnDisable();
|
||
ClearButtons();
|
||
GameManager.Instance?.SetPause(false);
|
||
}
|
||
}
|
||
}
|
||
|