Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- завершен спринт 24 марта - 2 апреля с опережением плана и закрытием задач раньше срока - расширена и стабилизирована система призов: добавлены и доработаны QR-tier конфиги, новые типы и модели данных, сохранение `qrCodes` в `prizes.json`, fallback-логика и синхронизация через `PrizeSystemManager`, `PrizeDatabase` и `LevelPrizeConfig` - переработан flow настройки QR-призов: добавлены `QRCodeSettingsModal` и связанный UI-код, разнесены короткий текст приза и полный текст сообщения, добавлены шаблоны форматирования, корректная подстановка текста в QR и в UI-модалках, а также сохранение конфигурации между запусками - доработан flow суперприза и текстовых настроек: добавлены fallback-правила показа, корректная обработка пустых строк и строк из одних пробелов, обновлены binders и показ текста в меню - реализована и обновлена отдельная настройка витрины: добавлена `PrizeStandSettingsModal`, обновлена навигация строк, логика выбора ячейки и порогов очков, циклический выбор ячеек, ускорение изменения значений и исправления для keyboard/gamepad UX - доработана игровая модалка витрины `PrizeStandModal`: добавлен вывод очков игрока, форматируемый текст счета, улучшено отображение ячеек и связанная логика выбора приза - исправлен подсчет очков и интеграция с prize flow: `ScoreSystem` переведен на событийное обновление текущего счета, добавлены `CurrentScore`, `CurrentScoreChanged` и корректный display score, витрина и связанные UI-элементы теперь получают актуальные очки, а не нулевые или несвоевременные preview-значения - переработаны модальные окна и навигация UI: улучшены `Modal`, `ModalManager`, `UIRowPointer`, `RulesModal`, `QRCodeModal`, `PrizeStandModal`, `LevelPrizeSettingsModal` и др., чтобы фокус, указатель и submit работали консистентно без мыши - обновлен win/prize UI: доработаны `WinBannerController`, `GameEndUIController`, `MainMenu`, `QRCodeUIController`, `TopPanelTextController` и настройки UI, улучшены тексты баннеров, отображение призов и итогового счета - обновлены сцены и ассеты под новый flow: `MainMenu`, `Level UI`, prefab'ы строк настроек и кнопок, глобальные префабы, шрифты TMP, иконки, recovery-данные и ресурсы UI - добавлены вспомогательные UI-компоненты и служебные скрипты: `ButtonTextFocusColor`, `SelectableDescriptionTarget`, `QRTier` и другие изменения, поддерживающие новый конфигурируемый prize UI
194 lines
6.6 KiB
C#
194 lines
6.6 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
using RR.Common.Prizes;
|
||
using RR.Common.Managers;
|
||
using RR.Game2D.Core.Managers;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Модальное окно для отображения QR-кодов.
|
||
/// Создаёт Image-объекты с QR-кодами динамически в указанном контейнере.
|
||
/// </summary>
|
||
public class QRCodeModal : Modal
|
||
{
|
||
/// <summary>Текущий экземпляр на сцене. Регистрируется автоматически.</summary>
|
||
public static QRCodeModal Current { get; private set; }
|
||
|
||
[Header("Контент")]
|
||
[Tooltip("Контейнер для размещения QR-кодов. QR-коды создаются динамически.")]
|
||
[SerializeField] private RectTransform qrCodesContainer;
|
||
[Tooltip("Текст для показа названия приза. Если не назначен, можно оставить пустым.")]
|
||
[SerializeField] private TMP_Text prizeText;
|
||
[Tooltip("Шаблон текста приза. Если содержит '{0}', он будет заменён на название приза. Иначе значение считается токеном для замены внутри текста TMP.")]
|
||
[SerializeField] private string prizeTextTemplatePlaceholder = "{0}";
|
||
|
||
private readonly List<GameObject> _createdObjects = new();
|
||
private string _defaultPrizeTextTemplate;
|
||
private bool _defaultPrizeTextActive;
|
||
|
||
protected override void Awake()
|
||
{
|
||
Current = this;
|
||
base.Awake(); // деактивирует GO, но ссылка Current остаётся валидной
|
||
|
||
if (prizeText != null)
|
||
{
|
||
_defaultPrizeTextTemplate = prizeText.text;
|
||
_defaultPrizeTextActive = prizeText.gameObject.activeSelf;
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (Current == this) Current = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Показывает модальное окно с указанными QR-кодами.
|
||
/// </summary>
|
||
/// <param name="qrTextures">Массив текстур QR-кодов для отображения.</param>
|
||
public void ShowQRCodes(Texture2D[] qrTextures, QRTier tier, float gameTime, int finalScore, bool showFinalScore)
|
||
{
|
||
ClearQRCodes();
|
||
|
||
if (qrCodesContainer == null)
|
||
{
|
||
Debug.LogWarning("[QRCodeModal] Контейнер для QR-кодов не назначен.");
|
||
return;
|
||
}
|
||
|
||
if (qrTextures == null || qrTextures.Length == 0)
|
||
{
|
||
Debug.LogWarning("[QRCodeModal] Нет QR-кодов для отображения.");
|
||
return;
|
||
}
|
||
|
||
foreach (Texture2D tex in qrTextures)
|
||
{
|
||
if (tex == null) continue;
|
||
|
||
GameObject qrObj = new GameObject("QRCode", typeof(RectTransform), typeof(Image));
|
||
qrObj.transform.SetParent(qrCodesContainer, false);
|
||
|
||
Sprite sprite = Sprite.Create(
|
||
tex,
|
||
new Rect(0, 0, tex.width, tex.height),
|
||
new Vector2(0.5f, 0.5f));
|
||
|
||
Image image = qrObj.GetComponent<Image>();
|
||
image.sprite = sprite;
|
||
image.raycastTarget = false;
|
||
|
||
_createdObjects.Add(qrObj);
|
||
}
|
||
|
||
UpdatePrizeText(tier);
|
||
|
||
Open();
|
||
GameManager.Instance?.SetPause(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Очищает созданные QR-код объекты.
|
||
/// </summary>
|
||
private void ClearQRCodes()
|
||
{
|
||
foreach (GameObject obj in _createdObjects)
|
||
{
|
||
if (obj != null) Destroy(obj);
|
||
}
|
||
_createdObjects.Clear();
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
base.Close();
|
||
ClearQRCodes();
|
||
ResetPrizeText();
|
||
GameManager.Instance?.SetPause(false);
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
base.OnDisable();
|
||
ClearQRCodes();
|
||
ResetPrizeText();
|
||
GameManager.Instance?.SetPause(false);
|
||
}
|
||
|
||
private void UpdatePrizeText(QRTier tier)
|
||
{
|
||
if (prizeText == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
PrizeSystemManager prizeSystemManager = PrizeSystemManager.Instance;
|
||
string prizeLine = prizeSystemManager != null
|
||
? prizeSystemManager.GetQRCodeModalPrizeText(tier)
|
||
: string.Empty;
|
||
|
||
bool hasPrizeLine = !string.IsNullOrWhiteSpace(prizeLine);
|
||
prizeText.gameObject.SetActive(hasPrizeLine);
|
||
if (hasPrizeLine)
|
||
{
|
||
prizeText.text = ApplyPrizeTextTemplate(prizeLine);
|
||
}
|
||
}
|
||
|
||
private void ResetPrizeText()
|
||
{
|
||
if (prizeText == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
prizeText.text = _defaultPrizeTextTemplate;
|
||
prizeText.gameObject.SetActive(_defaultPrizeTextActive);
|
||
}
|
||
|
||
private string ApplyPrizeTextTemplate(string prizeLine)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(prizeTextTemplatePlaceholder) &&
|
||
prizeTextTemplatePlaceholder.Contains("{0}"))
|
||
{
|
||
return prizeTextTemplatePlaceholder.Replace("{0}", prizeLine);
|
||
}
|
||
|
||
string template = _defaultPrizeTextTemplate;
|
||
if (string.IsNullOrWhiteSpace(template) && prizeText != null)
|
||
{
|
||
template = prizeText.text;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(template))
|
||
{
|
||
return prizeLine;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(prizeTextTemplatePlaceholder) &&
|
||
template.Contains(prizeTextTemplatePlaceholder))
|
||
{
|
||
return template.Replace(prizeTextTemplatePlaceholder, prizeLine);
|
||
}
|
||
|
||
if (template.Contains("{0}"))
|
||
{
|
||
return template.Replace("{0}", prizeLine);
|
||
}
|
||
|
||
string trimmedTemplate = template.TrimEnd();
|
||
if (trimmedTemplate.EndsWith(":") || trimmedTemplate.EndsWith("-"))
|
||
{
|
||
return $"{template} {prizeLine}".Trim();
|
||
}
|
||
|
||
return prizeLine;
|
||
}
|
||
}
|
||
}
|