Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- доработан flow призовой витрины и связанного UI: добавлены `PrizeStandButtonView` и `RulesPrizeListPage`, обновлены `PrizeStandController`, `PrizeStandPrize`, `PrizeStandModal`, `PrizeStandSettingsModal`, `LevelPrizeSettingsModal`, `QRCodeSettingsModal`, `BalanceDisplay`, `ButtonTextFocusColor` и связанные prefab'ы, чтобы улучшить настройку, навигацию, отображение кнопок и prize flow - расширена система настроек и сохранения значений: `OptionPicker` получил отдельный формат `ValueSelector`, обновлены `SettingsDatabase`, `SettingEntryDrawer`, `SettingUIItem`, `SettingsUIGenerator`, `ValueSelectorRow` и `MainSettingsDatabase.asset`, а сохранение option picker-значений, включая `gameplay.gameDifficulty`, исправлено и синхронизировано с остальными настройками - добавлены универсальные спавнеры сущностей: реализованы `EntitySpawner` и `EntitySpawnerManager` с поддержкой весов префабов, точек спавна, заполнения по проценту, ограничений на количество и автоспавна при инициализации - доработана логика игрового спавна и сложности: процент инициализационного спавна может зависеть от настройки `gameplay.gameDifficulty` с градациями 25/50/75/100, в одном цикле массового спавна повторно не используется та же точка, а gizmo спавнера теперь показывает только сами точки без линий - упрощена работа в редакторе Unity и добавлены editor-инструменты: в `GameManager` лицензия пропускается при запуске в `UNITY_EDITOR`, а `HierarchySelectionCounter`, `HierarchySelectionCountOverlay` и `HierarchyBulkRenamerWindow` добавляют подсчет выделения, realtime overlay в `SceneView` и массовое переименование объектов по правилам нумерации из Unity Project Settings - синхронизированы сцены, ресурсы и UI-ассеты под новый flow: обновлены `MainMenu`, `Level UI`, уровни `1-1` и `1-2`, строки настроек, кнопки, TMP/font assets, изображения UI, `prizes.json` и recovery-данные после правок интерфейса и логики - очищены и переразложены часть внешних и служебных ресурсов: удалён пакет `Lava_Flowing_Shader`, переименованы/заменены отдельные UI-prefab'ы и добавлены сопутствующие проектные артефакты, которые сейчас используются обновлённой конфигурацией проекта
728 lines
26 KiB
C#
728 lines
26 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.InputSystem;
|
||
using UnityEngine.UI;
|
||
using RR.Common.Managers;
|
||
using RR.Common.Prizes;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Модальное окно настройки приза за конкретный уровень.
|
||
/// Для QR настраивает ранг на уровне, для витрины редактирует общую игровую конфигурацию.
|
||
/// </summary>
|
||
public class LevelPrizeSettingsModal : Modal
|
||
{
|
||
[Header("UI — Общее")]
|
||
[SerializeField] private TMP_Text titleText;
|
||
[SerializeField] private TMP_Text prizeTypeLabel;
|
||
[SerializeField] private TMP_Text detailsText;
|
||
|
||
[Header("UI — QR")]
|
||
[SerializeField] private GameObject qrParamsGroup;
|
||
[SerializeField] private TMP_Text qrTierLabel;
|
||
|
||
[Header("UI — Параметры хоппера")]
|
||
[SerializeField] private GameObject hopperParamsGroup;
|
||
[SerializeField] private TMP_Text hopperCapsuleCountLabel;
|
||
|
||
[Header("UI — Параметры витрины")]
|
||
[SerializeField] private GameObject prizeStandParamsGroup;
|
||
[SerializeField] private TMP_Text prizeStandStatusLabel;
|
||
[SerializeField] private TMP_Text prizeStandBoxCountLabel;
|
||
[SerializeField] private TMP_Text prizeStandSelectedBoxLabel;
|
||
[SerializeField] private TMP_Text prizeStandThresholdLabel;
|
||
|
||
[Header("Строки (RectTransform)")]
|
||
[SerializeField] private RectTransform prizeTypeRow;
|
||
[SerializeField] private RectTransform qrTierRow;
|
||
[SerializeField] private RectTransform hopperParamRow;
|
||
[SerializeField] private RectTransform prizeStandBoxCountRow;
|
||
[SerializeField] private RectTransform prizeStandSelectedBoxRow;
|
||
[SerializeField] private RectTransform prizeStandThresholdRow;
|
||
|
||
[Header("Указатель")]
|
||
[SerializeField] private UIRowPointer rowPointer;
|
||
|
||
[Header("Кнопка закрытия")]
|
||
[SerializeField] private Button closeModalButton;
|
||
|
||
[Header("Навигация (Input System)")]
|
||
[SerializeField] private InputActionReference navigateAction;
|
||
[SerializeField] private float deadZone = 0.5f;
|
||
|
||
[Header("QR Tier Colors")]
|
||
[SerializeField] private Color bronzeTierColor = new Color32(205, 127, 50, 255);
|
||
[SerializeField] private Color silverTierColor = new Color32(192, 192, 192, 255);
|
||
[SerializeField] private Color goldTierColor = new Color32(255, 215, 0, 255);
|
||
|
||
[Header("Лимиты")]
|
||
[SerializeField] private int maxCapsules = 10;
|
||
[SerializeField] private int maxBoxes = 48;
|
||
[SerializeField] private int defaultPrizeStandBoxesCount = 5;
|
||
[SerializeField] private int scoreStep = 50;
|
||
|
||
private const int ROW_TYPE = 0;
|
||
private const int ROW_QR_TIER = 1;
|
||
private const int ROW_HOPPER_PARAM = 2;
|
||
private const int ROW_STAND_BOX_COUNT = 3;
|
||
private const int ROW_STAND_SELECTED_BOX = 4;
|
||
private const int ROW_STAND_THRESHOLD = 5;
|
||
private const int ROW_CLOSE = 6;
|
||
private const int ROW_COUNT = 7;
|
||
|
||
private static readonly PrizeType[] PrizeTypes = (PrizeType[])Enum.GetValues(typeof(PrizeType));
|
||
private static readonly QRTier[] QRTiers = { QRTier.Bronze, QRTier.Silver, QRTier.Gold };
|
||
|
||
private string _levelName;
|
||
private LevelPrizeConfig _config;
|
||
private PrizeStandSettings _prizeStandSettings;
|
||
private int _currentTypeIndex;
|
||
private int _currentTierIndex;
|
||
private int _activeRow;
|
||
private int _selectedPrizeStandBoxIndex;
|
||
private bool _axisHeldX;
|
||
private bool _axisHeldY;
|
||
private Selectable _pendingFocusTarget;
|
||
private int _pendingFocusFrames;
|
||
private Selectable _prizeTypeRowSelectable;
|
||
private Selectable _qrTierRowSelectable;
|
||
private Selectable _hopperParamRowSelectable;
|
||
private Selectable _prizeStandBoxCountRowSelectable;
|
||
private Selectable _prizeStandSelectedBoxRowSelectable;
|
||
private Selectable _prizeStandThresholdRowSelectable;
|
||
|
||
public event Action<string> OnApplied;
|
||
|
||
protected override void OnEnable()
|
||
{
|
||
base.OnEnable();
|
||
|
||
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;
|
||
}
|
||
|
||
_axisHeldX = false;
|
||
_axisHeldY = false;
|
||
_pendingFocusTarget = null;
|
||
_pendingFocusFrames = 0;
|
||
base.OnDisable();
|
||
}
|
||
|
||
protected override void OnReturnedToTop()
|
||
{
|
||
_axisHeldX = false;
|
||
_axisHeldY = false;
|
||
_pendingFocusTarget = null;
|
||
_pendingFocusFrames = 0;
|
||
RefreshRowHighlights();
|
||
}
|
||
|
||
private void LateUpdate()
|
||
{
|
||
if (!_isOpen)
|
||
{
|
||
return;
|
||
}
|
||
|
||
ApplyPendingFocus();
|
||
}
|
||
|
||
private void OnNavigatePerformed(InputAction.CallbackContext ctx)
|
||
{
|
||
if (!_isOpen) return;
|
||
|
||
Vector2 v = ctx.ReadValue<Vector2>();
|
||
|
||
if (Mathf.Abs(v.y) >= deadZone)
|
||
{
|
||
if (!_axisHeldY)
|
||
{
|
||
_axisHeldY = true;
|
||
MoveRow(v.y > 0f ? -1 : 1);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_axisHeldY = false;
|
||
}
|
||
|
||
if (Mathf.Abs(v.x) >= deadZone)
|
||
{
|
||
if (!_axisHeldX)
|
||
{
|
||
_axisHeldX = true;
|
||
HandleLeftRight(v.x > 0f ? 1 : -1);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_axisHeldX = false;
|
||
}
|
||
}
|
||
|
||
private void OnNavigateCanceled(InputAction.CallbackContext ctx)
|
||
{
|
||
_axisHeldX = false;
|
||
_axisHeldY = false;
|
||
}
|
||
|
||
private void MoveRow(int delta)
|
||
{
|
||
int next = _activeRow;
|
||
for (int i = 0; i < ROW_COUNT; i++)
|
||
{
|
||
next = (next + delta + ROW_COUNT) % ROW_COUNT;
|
||
if (IsRowVisible(next))
|
||
{
|
||
_activeRow = next;
|
||
break;
|
||
}
|
||
}
|
||
|
||
RefreshRowHighlights();
|
||
}
|
||
|
||
private bool IsRowVisible(int row)
|
||
{
|
||
return row switch
|
||
{
|
||
ROW_QR_TIER => _config.type == PrizeType.QRCode,
|
||
ROW_HOPPER_PARAM => _config.type == PrizeType.Hopper,
|
||
ROW_STAND_BOX_COUNT => HasVisiblePrizeStandLevelSettings(),
|
||
ROW_STAND_SELECTED_BOX => HasVisiblePrizeStandLevelSettings() && GetPrizeStandBoxes().Count > 0,
|
||
ROW_STAND_THRESHOLD => HasVisiblePrizeStandLevelSettings() && GetPrizeStandBoxes().Count > 0,
|
||
_ => true
|
||
};
|
||
}
|
||
|
||
private void HandleLeftRight(int direction)
|
||
{
|
||
switch (_activeRow)
|
||
{
|
||
case ROW_TYPE:
|
||
CyclePrizeType(direction);
|
||
break;
|
||
case ROW_QR_TIER:
|
||
AdjustQrTier(direction);
|
||
break;
|
||
case ROW_HOPPER_PARAM:
|
||
AdjustCapsules(direction);
|
||
break;
|
||
case ROW_STAND_BOX_COUNT:
|
||
AdjustPrizeStandBoxCount(direction);
|
||
break;
|
||
case ROW_STAND_SELECTED_BOX:
|
||
AdjustSelectedPrizeStandBox(direction);
|
||
break;
|
||
case ROW_STAND_THRESHOLD:
|
||
AdjustPrizeStandThreshold(direction);
|
||
break;
|
||
}
|
||
}
|
||
|
||
public void OpenForLevel(string levelName)
|
||
{
|
||
_levelName = levelName;
|
||
|
||
PrizeDatabase db = PrizeSystemManager.Instance?.Database;
|
||
_config = db != null
|
||
? CopyConfig(db.GetConfig(levelName))
|
||
: new LevelPrizeConfig();
|
||
_prizeStandSettings = db != null
|
||
? CopyPrizeStandSettings(db.GetPrizeStandSettings())
|
||
: CreateDefaultPrizeStandSettings();
|
||
|
||
EnsurePrizeStandSettingsInitialized();
|
||
|
||
_currentTypeIndex = Array.IndexOf(PrizeTypes, _config.type);
|
||
if (_currentTypeIndex < 0)
|
||
_currentTypeIndex = 0;
|
||
|
||
_currentTierIndex = GetTierIndex(_config.qrTier);
|
||
_selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, Mathf.Max(0, GetPrizeStandBoxes().Count - 1));
|
||
_activeRow = ROW_TYPE;
|
||
|
||
AutoResolveRows();
|
||
EnsureRowSelectables();
|
||
RefreshUI();
|
||
RefreshRowHighlights();
|
||
Open();
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
Save();
|
||
base.Close();
|
||
}
|
||
|
||
private void CyclePrizeType(int direction)
|
||
{
|
||
_currentTypeIndex = (_currentTypeIndex + direction + PrizeTypes.Length) % PrizeTypes.Length;
|
||
_config.type = PrizeTypes[_currentTypeIndex];
|
||
|
||
if (_config.type == PrizeType.QRCode && _config.qrTier == QRTier.None)
|
||
_config.qrTier = QRTiers[0];
|
||
|
||
EnsurePrizeStandSettingsInitialized();
|
||
if (!IsRowVisible(_activeRow))
|
||
_activeRow = ROW_TYPE;
|
||
|
||
RefreshUI();
|
||
RefreshRowHighlights();
|
||
}
|
||
|
||
private void AdjustQrTier(int direction)
|
||
{
|
||
_currentTierIndex = (_currentTierIndex + direction + QRTiers.Length) % QRTiers.Length;
|
||
_config.qrTier = QRTiers[_currentTierIndex];
|
||
RefreshUI();
|
||
}
|
||
|
||
private void AdjustCapsules(int delta)
|
||
{
|
||
_config.hopperCapsuleCount = Mathf.Clamp(_config.hopperCapsuleCount + delta, 1, maxCapsules);
|
||
RefreshUI();
|
||
}
|
||
|
||
private void AdjustPrizeStandBoxCount(int delta)
|
||
{
|
||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||
int currentCount = boxes.Count;
|
||
int nextCount = Mathf.Clamp(currentCount + delta, 1, maxBoxes);
|
||
if (nextCount == currentCount)
|
||
return;
|
||
|
||
if (nextCount > currentCount)
|
||
{
|
||
for (int i = currentCount + 1; i <= nextCount; i++)
|
||
boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
|
||
}
|
||
else
|
||
{
|
||
boxes.RemoveRange(nextCount, currentCount - nextCount);
|
||
}
|
||
|
||
NormalizePrizeStandBoxes();
|
||
_selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, boxes.Count - 1);
|
||
RefreshUI();
|
||
RefreshRowHighlights();
|
||
}
|
||
|
||
private void AdjustSelectedPrizeStandBox(int delta)
|
||
{
|
||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||
if (boxes.Count == 0)
|
||
return;
|
||
|
||
_selectedPrizeStandBoxIndex = (_selectedPrizeStandBoxIndex + delta + boxes.Count) % boxes.Count;
|
||
RefreshUI();
|
||
}
|
||
|
||
private void AdjustPrizeStandThreshold(int direction)
|
||
{
|
||
PrizeStandBoxConfig selectedBox = GetSelectedPrizeStandBox();
|
||
if (selectedBox == null)
|
||
return;
|
||
|
||
selectedBox.requiredScore = Mathf.Max(0, selectedBox.requiredScore + direction * scoreStep);
|
||
RefreshUI();
|
||
}
|
||
|
||
private void Save()
|
||
{
|
||
if (string.IsNullOrEmpty(_levelName) || _config == null)
|
||
return;
|
||
|
||
PrizeDatabase db = PrizeSystemManager.Instance?.Database;
|
||
PrizeSystemManager manager = PrizeSystemManager.Instance;
|
||
if (db == null || manager == null)
|
||
{
|
||
Debug.LogError("[LevelPrizeSettingsModal] PrizeDatabase недоступна.");
|
||
return;
|
||
}
|
||
|
||
db.SetConfig(_levelName, _config);
|
||
Debug.Log($"[LevelPrizeSettingsModal] Уровень «{_levelName}» → приз «{_config.type}» сохранён.");
|
||
OnApplied?.Invoke(_levelName);
|
||
}
|
||
|
||
private void RefreshUI()
|
||
{
|
||
EnsurePrizeStandSettingsInitialized();
|
||
|
||
if (titleText)
|
||
titleText.text = $"Приз за уровень {_levelName}";
|
||
|
||
if (prizeTypeLabel)
|
||
prizeTypeLabel.text = GetPrizeTypeName(_config.type);
|
||
|
||
if (qrParamsGroup) qrParamsGroup.SetActive(_config.type == PrizeType.QRCode);
|
||
if (hopperParamsGroup) hopperParamsGroup.SetActive(_config.type == PrizeType.Hopper);
|
||
if (prizeStandParamsGroup) prizeStandParamsGroup.SetActive(HasVisiblePrizeStandLevelSettings());
|
||
|
||
if (qrTierLabel)
|
||
{
|
||
qrTierLabel.text = GetTierName(_config.qrTier);
|
||
qrTierLabel.color = GetTierColor(_config.qrTier);
|
||
}
|
||
|
||
if (hopperCapsuleCountLabel)
|
||
hopperCapsuleCountLabel.text = _config.hopperCapsuleCount.ToString();
|
||
|
||
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
|
||
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
|
||
: new PrizeStandConnectionInfo(false, 0);
|
||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||
PrizeStandBoxConfig selectedBox = GetSelectedPrizeStandBox();
|
||
|
||
if (prizeStandStatusLabel)
|
||
prizeStandStatusLabel.text = connectionInfo.IsConnected
|
||
? $"Подключена ({connectionInfo.ConnectedBoxesCount} ячеек)"
|
||
: "Не подключена";
|
||
|
||
if (prizeStandBoxCountLabel)
|
||
prizeStandBoxCountLabel.text = boxes.Count.ToString();
|
||
|
||
if (prizeStandSelectedBoxLabel)
|
||
prizeStandSelectedBoxLabel.text = selectedBox != null ? selectedBox.boxNumber.ToString() : "-";
|
||
|
||
if (prizeStandThresholdLabel)
|
||
prizeStandThresholdLabel.text = selectedBox != null ? selectedBox.requiredScore.ToString() : "-";
|
||
|
||
if (detailsText)
|
||
detailsText.text = BuildDetailsText(connectionInfo, boxes, selectedBox);
|
||
}
|
||
|
||
private string BuildDetailsText(PrizeStandConnectionInfo connectionInfo, List<PrizeStandBoxConfig> boxes, PrizeStandBoxConfig selectedBox)
|
||
{
|
||
return _config.type switch
|
||
{
|
||
PrizeType.QRCode => $"Ранг QR-приза: {GetTierName(_config.qrTier)}",
|
||
PrizeType.Hopper => $"Капсул за выдачу: {_config.hopperCapsuleCount}",
|
||
PrizeType.PrizeStand => BuildPrizeStandDetails(connectionInfo, boxes, selectedBox),
|
||
_ => "Для уровня приз не назначен."
|
||
};
|
||
}
|
||
|
||
private string BuildPrizeStandDetails(PrizeStandConnectionInfo connectionInfo, List<PrizeStandBoxConfig> boxes, PrizeStandBoxConfig selectedBox)
|
||
{
|
||
string connectionText = connectionInfo.IsConnected
|
||
? $"Витрина подключена, доступно на устройстве: {connectionInfo.ConnectedBoxesCount} ячеек."
|
||
: "Витрина не подключена.";
|
||
|
||
if (HasVisiblePrizeStandLevelSettings())
|
||
{
|
||
string selectedText = selectedBox != null
|
||
? $"Выбрана ячейка {selectedBox.boxNumber}, порог: {selectedBox.requiredScore} очков."
|
||
: "Ячейки не настроены.";
|
||
|
||
return $"{connectionText}\nСконфигурировано ячеек: {boxes.Count}\n{selectedText}";
|
||
}
|
||
|
||
return $"{connectionText}\nПараметры витрины настраиваются отдельно в глобальных настройках витрины.";
|
||
}
|
||
|
||
private void RefreshRowHighlights()
|
||
{
|
||
Selectable selectableToFocus = _activeRow switch
|
||
{
|
||
ROW_TYPE => _prizeTypeRowSelectable,
|
||
ROW_QR_TIER => _qrTierRowSelectable,
|
||
ROW_HOPPER_PARAM => _hopperParamRowSelectable,
|
||
ROW_STAND_BOX_COUNT => _prizeStandBoxCountRowSelectable,
|
||
ROW_STAND_SELECTED_BOX => _prizeStandSelectedBoxRowSelectable,
|
||
ROW_STAND_THRESHOLD => _prizeStandThresholdRowSelectable,
|
||
ROW_CLOSE => closeModalButton,
|
||
_ => _prizeTypeRowSelectable
|
||
};
|
||
|
||
if (_activeRow == ROW_CLOSE)
|
||
{
|
||
if (rowPointer) rowPointer.Hide();
|
||
}
|
||
else
|
||
{
|
||
if (rowPointer)
|
||
{
|
||
RectTransform target = GetActiveRowTransform();
|
||
if (target) rowPointer.SetTarget(target);
|
||
}
|
||
}
|
||
|
||
FocusSelectable(selectableToFocus);
|
||
}
|
||
|
||
private RectTransform GetActiveRowTransform()
|
||
{
|
||
return _activeRow switch
|
||
{
|
||
ROW_TYPE => prizeTypeRow,
|
||
ROW_QR_TIER => qrTierRow != null ? qrTierRow : prizeTypeRow,
|
||
ROW_HOPPER_PARAM => hopperParamRow != null ? hopperParamRow : prizeTypeRow,
|
||
ROW_STAND_BOX_COUNT => prizeStandBoxCountRow,
|
||
ROW_STAND_SELECTED_BOX => prizeStandSelectedBoxRow,
|
||
ROW_STAND_THRESHOLD => prizeStandThresholdRow,
|
||
_ => prizeTypeRow
|
||
};
|
||
}
|
||
|
||
private void AutoResolveRows()
|
||
{
|
||
if (prizeTypeRow == null && prizeTypeLabel != null)
|
||
prizeTypeRow = prizeTypeLabel.transform.parent as RectTransform;
|
||
|
||
if (qrTierRow == null && qrTierLabel != null)
|
||
qrTierRow = qrTierLabel.transform.parent as RectTransform;
|
||
|
||
if (hopperParamRow == null && hopperCapsuleCountLabel != null)
|
||
hopperParamRow = hopperCapsuleCountLabel.transform.parent as RectTransform;
|
||
|
||
if (prizeStandBoxCountRow == null && prizeStandBoxCountLabel != null)
|
||
prizeStandBoxCountRow = prizeStandBoxCountLabel.transform.parent as RectTransform;
|
||
|
||
if (prizeStandSelectedBoxRow == null && prizeStandSelectedBoxLabel != null)
|
||
prizeStandSelectedBoxRow = prizeStandSelectedBoxLabel.transform.parent as RectTransform;
|
||
|
||
if (prizeStandThresholdRow == null && prizeStandThresholdLabel != null)
|
||
prizeStandThresholdRow = prizeStandThresholdLabel.transform.parent as RectTransform;
|
||
}
|
||
|
||
private void EnsureRowSelectables()
|
||
{
|
||
_prizeTypeRowSelectable = EnsureRowSelectable(prizeTypeRow);
|
||
_qrTierRowSelectable = EnsureRowSelectable(qrTierRow);
|
||
_hopperParamRowSelectable = EnsureRowSelectable(hopperParamRow);
|
||
_prizeStandBoxCountRowSelectable = EnsureRowSelectable(prizeStandBoxCountRow);
|
||
_prizeStandSelectedBoxRowSelectable = EnsureRowSelectable(prizeStandSelectedBoxRow);
|
||
_prizeStandThresholdRowSelectable = EnsureRowSelectable(prizeStandThresholdRow);
|
||
}
|
||
|
||
private Selectable EnsureRowSelectable(RectTransform row)
|
||
{
|
||
if (row == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
Selectable selectable = row.GetComponent<Selectable>();
|
||
if (selectable == null)
|
||
{
|
||
selectable = row.gameObject.AddComponent<Selectable>();
|
||
selectable.transition = Selectable.Transition.None;
|
||
selectable.navigation = new Navigation { mode = Navigation.Mode.None };
|
||
}
|
||
|
||
selectable.interactable = true;
|
||
return selectable;
|
||
}
|
||
|
||
private void FocusSelectable(Selectable selectable)
|
||
{
|
||
if (selectable == null || !selectable.gameObject.activeInHierarchy)
|
||
{
|
||
return;
|
||
}
|
||
|
||
SetFirstSelected(selectable);
|
||
_pendingFocusTarget = selectable;
|
||
_pendingFocusFrames = 2;
|
||
ApplyPendingFocus();
|
||
}
|
||
|
||
private void ApplyPendingFocus()
|
||
{
|
||
if (_pendingFocusTarget == null || _pendingFocusFrames <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (EventSystem.current == null || !_pendingFocusTarget.gameObject.activeInHierarchy)
|
||
{
|
||
_pendingFocusTarget = null;
|
||
_pendingFocusFrames = 0;
|
||
return;
|
||
}
|
||
|
||
if (EventSystem.current.currentSelectedGameObject != _pendingFocusTarget.gameObject)
|
||
{
|
||
EventSystem.current.SetSelectedGameObject(null);
|
||
EventSystem.current.SetSelectedGameObject(_pendingFocusTarget.gameObject);
|
||
_pendingFocusTarget.Select();
|
||
}
|
||
|
||
_pendingFocusFrames--;
|
||
if (_pendingFocusFrames <= 0)
|
||
{
|
||
_pendingFocusTarget = null;
|
||
}
|
||
}
|
||
|
||
private bool HasVisiblePrizeStandLevelSettings()
|
||
{
|
||
if (_config == null || _config.type != PrizeType.PrizeStand)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return prizeStandParamsGroup != null
|
||
&& (prizeStandBoxCountRow != null || prizeStandSelectedBoxRow != null || prizeStandThresholdRow != null);
|
||
}
|
||
|
||
private void EnsurePrizeStandSettingsInitialized()
|
||
{
|
||
_prizeStandSettings ??= CreateDefaultPrizeStandSettings();
|
||
_prizeStandSettings.boxes ??= new List<PrizeStandBoxConfig>();
|
||
|
||
if (_prizeStandSettings.boxes.Count == 0)
|
||
{
|
||
int initialCount = defaultPrizeStandBoxesCount;
|
||
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
|
||
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
|
||
: new PrizeStandConnectionInfo(false, 0);
|
||
if (connectionInfo.IsConnected && connectionInfo.ConnectedBoxesCount > 0)
|
||
initialCount = Mathf.Clamp(connectionInfo.ConnectedBoxesCount, 1, maxBoxes);
|
||
|
||
for (int i = 1; i <= initialCount; i++)
|
||
_prizeStandSettings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
|
||
}
|
||
|
||
NormalizePrizeStandBoxes();
|
||
}
|
||
|
||
private void NormalizePrizeStandBoxes()
|
||
{
|
||
List<PrizeStandBoxConfig> boxes = _prizeStandSettings.boxes;
|
||
boxes.Sort((a, b) => a.boxNumber.CompareTo(b.boxNumber));
|
||
|
||
for (int i = 0; i < boxes.Count; i++)
|
||
{
|
||
if (boxes[i] == null)
|
||
boxes[i] = new PrizeStandBoxConfig();
|
||
|
||
boxes[i].boxNumber = i + 1;
|
||
boxes[i].requiredScore = Mathf.Max(0, boxes[i].requiredScore);
|
||
}
|
||
}
|
||
|
||
private List<PrizeStandBoxConfig> GetPrizeStandBoxes()
|
||
{
|
||
EnsurePrizeStandSettingsInitialized();
|
||
return _prizeStandSettings.boxes;
|
||
}
|
||
|
||
private PrizeStandBoxConfig GetSelectedPrizeStandBox()
|
||
{
|
||
List<PrizeStandBoxConfig> boxes = GetPrizeStandBoxes();
|
||
if (boxes.Count == 0)
|
||
return null;
|
||
|
||
_selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, boxes.Count - 1);
|
||
return boxes[_selectedPrizeStandBoxIndex];
|
||
}
|
||
|
||
private static string GetPrizeTypeName(PrizeType type)
|
||
{
|
||
return type switch
|
||
{
|
||
PrizeType.None => "Нет приза",
|
||
PrizeType.QRCode => "QR-код",
|
||
PrizeType.Hopper => "Хоппер",
|
||
PrizeType.PrizeStand => "Витрина",
|
||
_ => type.ToString()
|
||
};
|
||
}
|
||
|
||
private static string GetTierName(QRTier tier)
|
||
{
|
||
return tier switch
|
||
{
|
||
QRTier.Bronze => "Бронзовый",
|
||
QRTier.Silver => "Серебряный",
|
||
QRTier.Gold => "Золотой",
|
||
_ => "Не задан"
|
||
};
|
||
}
|
||
|
||
private Color GetTierColor(QRTier tier)
|
||
{
|
||
return tier switch
|
||
{
|
||
QRTier.Bronze => bronzeTierColor,
|
||
QRTier.Silver => silverTierColor,
|
||
QRTier.Gold => goldTierColor,
|
||
_ => qrTierLabel != null ? qrTierLabel.color : Color.white
|
||
};
|
||
}
|
||
|
||
private static int GetTierIndex(QRTier tier)
|
||
{
|
||
int index = Array.IndexOf(QRTiers, tier);
|
||
return index >= 0 ? index : 0;
|
||
}
|
||
|
||
private static LevelPrizeConfig CopyConfig(LevelPrizeConfig src)
|
||
{
|
||
return new LevelPrizeConfig
|
||
{
|
||
type = src.type,
|
||
qrTier = src.qrTier,
|
||
hopperCapsuleCount = src.hopperCapsuleCount,
|
||
hopperDelayBetweenCapsules = src.hopperDelayBetweenCapsules
|
||
};
|
||
}
|
||
|
||
private static PrizeStandSettings CopyPrizeStandSettings(PrizeStandSettings src)
|
||
{
|
||
PrizeStandSettings copy = new PrizeStandSettings();
|
||
if (src?.boxes == null)
|
||
return copy;
|
||
|
||
for (int i = 0; i < src.boxes.Count; i++)
|
||
{
|
||
PrizeStandBoxConfig box = src.boxes[i];
|
||
if (box == null)
|
||
continue;
|
||
|
||
copy.boxes.Add(new PrizeStandBoxConfig
|
||
{
|
||
boxNumber = box.boxNumber,
|
||
requiredScore = box.requiredScore
|
||
});
|
||
}
|
||
|
||
return copy;
|
||
}
|
||
|
||
private PrizeStandSettings CreateDefaultPrizeStandSettings()
|
||
{
|
||
PrizeStandSettings settings = new PrizeStandSettings();
|
||
for (int i = 1; i <= defaultPrizeStandBoxesCount; i++)
|
||
{
|
||
settings.boxes.Add(new PrizeStandBoxConfig
|
||
{
|
||
boxNumber = i,
|
||
requiredScore = 0
|
||
});
|
||
}
|
||
|
||
return settings;
|
||
}
|
||
}
|
||
}
|