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-сцены
283 lines
12 KiB
C#
283 lines
12 KiB
C#
using System.Collections.Generic;
|
||
using System.IO;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.UI;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Модальное окно выбора уровня для настройки награды за его прохождение.
|
||
/// Строит список уровней из <see cref="SceneManager.sceneCountInBuildSettings"/> и
|
||
/// предоставляет выбор текущего уровня как контекст для дальнейшего редактирования призов.
|
||
/// </summary>
|
||
public class LevelSettingsModal : Modal
|
||
{
|
||
[Header("References")]
|
||
[SerializeField] private Transform contentRoot;
|
||
[SerializeField] private GameObject levelButtonPrefab;
|
||
|
||
[Header("Display")]
|
||
[SerializeField] private bool rebuildOnOpen = true;
|
||
[SerializeField] private bool includeOnlyLevelsFolder = true;
|
||
[SerializeField] private string levelsFolderName = "Levels";
|
||
[SerializeField] private List<string> ignoredSceneNames = new() { "MainMenu", "Level UI" };
|
||
|
||
[Header("Selection Visual")]
|
||
[SerializeField] private Vector3 normalScale = Vector3.one;
|
||
[SerializeField] private Vector3 selectedScale = new(1.05f, 1.05f, 1f);
|
||
|
||
[Header("Модальное окно настройки приза")]
|
||
[Tooltip("Ссылка на LevelPrizeSettingsModal. Открывается при нажатии на кнопку уровня.")]
|
||
[SerializeField] private LevelPrizeSettingsModal levelPrizeSettingsModal;
|
||
|
||
private readonly List<LevelButtonView> _spawnedButtons = new();
|
||
private LevelButtonView _selectedButton;
|
||
|
||
/// <summary>
|
||
/// Имя сцены выбранного уровня.
|
||
/// Используется внешними компонентами как ключ для загрузки или сохранения настроек приза.
|
||
/// </summary>
|
||
public string SelectedSceneName => _selectedButton != null ? _selectedButton.SceneName : string.Empty;
|
||
|
||
/// <summary>
|
||
/// Открывает окно выбора уровня.
|
||
/// Перед открытием при необходимости пересобирает список кнопок из актуального набора сцен в Build Settings.
|
||
/// </summary>
|
||
public override void Open()
|
||
{
|
||
if (rebuildOnOpen || _spawnedButtons.Count == 0)
|
||
{
|
||
Rebuild();
|
||
}
|
||
|
||
base.Open();
|
||
}
|
||
|
||
[ContextMenu("Rebuild Level Buttons")]
|
||
public void Rebuild()
|
||
{
|
||
if (!ValidateReferences())
|
||
{
|
||
return;
|
||
}
|
||
|
||
ClearButtons();
|
||
|
||
List<string> sceneNames = GetLevelSceneNames();
|
||
foreach (string t in sceneNames)
|
||
{
|
||
CreateButton(t);
|
||
}
|
||
|
||
if (_spawnedButtons.Count <= 0) return;
|
||
SelectButton(_spawnedButtons[0]);
|
||
SetFirstSelected(_spawnedButtons[0].Button);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, что в инспекторе назначены контейнер для кнопок и префаб кнопки уровня.
|
||
/// </summary>
|
||
/// <returns>
|
||
/// <see langword="true"/>, если окно готово к построению списка уровней;
|
||
/// иначе <see langword="false"/>.
|
||
/// </returns>
|
||
private bool ValidateReferences()
|
||
{
|
||
if (contentRoot == null)
|
||
{
|
||
Debug.LogError($"[{nameof(LevelSettingsModal)}] Content Root is not assigned on {name}.", this);
|
||
return false;
|
||
}
|
||
|
||
if (levelButtonPrefab == null)
|
||
{
|
||
Debug.LogError($"[{nameof(LevelSettingsModal)}] Level Button Prefab is not assigned on {name}.", this);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Создает кнопку для конкретной сцены уровня и настраивает ее отображение.
|
||
/// Нажатие на кнопку только выбирает уровень для последующей настройки награды.
|
||
/// </summary>
|
||
/// <param name="sceneName">Имя сцены уровня, отображаемое в интерфейсе.</param>
|
||
private void CreateButton(string sceneName)
|
||
{
|
||
GameObject buttonObject = Instantiate(levelButtonPrefab, contentRoot);
|
||
buttonObject.name = $"LevelButton_{sceneName}";
|
||
|
||
Button button = buttonObject.GetComponentInChildren<Button>(true);
|
||
if (button == null)
|
||
{
|
||
Debug.LogError(
|
||
$"[{nameof(LevelSettingsModal)}] Prefab '{levelButtonPrefab.name}' must contain a Button component.",
|
||
buttonObject);
|
||
Destroy(buttonObject);
|
||
return;
|
||
}
|
||
|
||
TMP_Text tmpText = buttonObject.GetComponentInChildren<TMP_Text>(true);
|
||
if (tmpText != null)
|
||
{
|
||
tmpText.text = sceneName;
|
||
}
|
||
else
|
||
{
|
||
Text legacyText = buttonObject.GetComponentInChildren<Text>(true);
|
||
if (legacyText != null)
|
||
{
|
||
legacyText.text = sceneName;
|
||
}
|
||
}
|
||
|
||
LevelButtonView buttonView = new(buttonObject.transform, sceneName, buttonObject.transform as RectTransform, button);
|
||
|
||
button.onClick.RemoveAllListeners();
|
||
button.onClick.AddListener(() => OnLevelButtonClicked(buttonView));
|
||
|
||
ApplySelectedVisual(buttonView, false);
|
||
_spawnedButtons.Add(buttonView);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обрабатывает нажатие на кнопку уровня.
|
||
/// Не запускает уровень, а только делает его текущим выбранным элементом для настройки приза.
|
||
/// </summary>
|
||
/// <param name="buttonView">Представление нажатой кнопки уровня.</param>
|
||
private void OnLevelButtonClicked(LevelButtonView buttonView)
|
||
{
|
||
SelectButton(buttonView);
|
||
Debug.Log($"[{nameof(LevelSettingsModal)}] Selected level: {buttonView.SceneName}");
|
||
|
||
// Открываем окно настройки приза для выбранного уровня
|
||
if (levelPrizeSettingsModal != null)
|
||
levelPrizeSettingsModal.OpenForLevel(buttonView.SceneName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновляет текущий выбранный уровень и переключает визуальное выделение между кнопками.
|
||
/// </summary>
|
||
/// <param name="buttonView">Кнопка уровня, которая должна стать активной.</param>
|
||
private void SelectButton(LevelButtonView buttonView)
|
||
{
|
||
if (_selectedButton != null)
|
||
{
|
||
ApplySelectedVisual(_selectedButton, false);
|
||
}
|
||
|
||
_selectedButton = buttonView;
|
||
|
||
if (_selectedButton != null)
|
||
{
|
||
ApplySelectedVisual(_selectedButton, true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Включает или отключает визуальное состояние выбранного уровня.
|
||
/// Используется, чтобы в интерфейсе было видно, для какого уровня сейчас настраивается награда.
|
||
/// </summary>
|
||
/// <param name="buttonView">Кнопка, для которой обновляется внешний вид.</param>
|
||
/// <param name="isSelected">
|
||
/// <see langword="true"/>, если кнопка должна быть показана как выбранная;
|
||
/// иначе <see langword="false"/>.
|
||
/// </param>
|
||
private void ApplySelectedVisual(LevelButtonView buttonView, bool isSelected)
|
||
{
|
||
if (buttonView.RectTransform != null)
|
||
{
|
||
buttonView.RectTransform.localScale = isSelected ? selectedScale : normalScale;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Собирает список имен сцен уровней, доступных для настройки наград.
|
||
/// Источником служат сцены, добавленные в Build Settings, с учетом настроенной фильтрации.
|
||
/// </summary>
|
||
/// <returns>Список имен сцен уровней для построения кнопок в окне настроек.</returns>
|
||
private List<string> GetLevelSceneNames()
|
||
{
|
||
var result = new List<string>();
|
||
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||
|
||
for (int i = 0; i < sceneCount; i++)
|
||
{
|
||
string scenePath = SceneUtility.GetScenePathByBuildIndex(i);
|
||
if (string.IsNullOrWhiteSpace(scenePath))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (includeOnlyLevelsFolder)
|
||
{
|
||
string folderToken = $"/{levelsFolderName}/";
|
||
if (!scenePath.Replace("\\", "/").Contains(folderToken))
|
||
{
|
||
continue;
|
||
}
|
||
}
|
||
|
||
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
|
||
if (ignoredSceneNames.Contains(sceneName))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
result.Add(sceneName);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Удаляет ранее созданные кнопки уровней и сбрасывает текущий выбор.
|
||
/// Используется перед полной пересборкой списка.
|
||
/// </summary>
|
||
private void ClearButtons()
|
||
{
|
||
foreach (LevelButtonView buttonView in _spawnedButtons)
|
||
{
|
||
if (buttonView.Root != null)
|
||
{
|
||
Destroy(buttonView.Root.gameObject);
|
||
}
|
||
}
|
||
|
||
_spawnedButtons.Clear();
|
||
_selectedButton = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Внутреннее представление кнопки уровня, используемое модальным окном
|
||
/// для хранения ссылки на UI-элементы и имя сцены выбранного уровня.
|
||
/// </summary>
|
||
private sealed class LevelButtonView
|
||
{
|
||
/// <summary>
|
||
/// Инициализирует представление кнопки уровня, используемое для выбора уровня
|
||
/// при настройке награды за его прохождение.
|
||
/// </summary>
|
||
/// <param name="root">Корневой объект экземпляра префаба кнопки.</param>
|
||
/// <param name="sceneName">Имя сцены уровня.</param>
|
||
/// <param name="rectTransform">Прямоугольная трансформация корневого UI-элемента.</param>
|
||
/// <param name="button">Компонент кнопки, принимающий пользовательский ввод.</param>
|
||
public LevelButtonView(Transform root, string sceneName, RectTransform rectTransform, Button button)
|
||
{
|
||
Root = root;
|
||
SceneName = sceneName;
|
||
RectTransform = rectTransform;
|
||
Button = button;
|
||
}
|
||
|
||
public Transform Root { get; }
|
||
public string SceneName { get; }
|
||
public RectTransform RectTransform { get; }
|
||
public Button Button { get; }
|
||
}
|
||
}
|
||
}
|