Danro-Jump/Assets/Scripts/Runtime/UI/MainMenu/PrizeLevelAddModalWindow.cs
Danil Iarosh c1b72854f4 Implement prize QR and service UI polish
Подробные изменения:

- Добавлена система призовых уровней и выдачи призов с QR-контуром Telegram, шаблоном сообщения и генерацией QR без внешних библиотек.

- Доработаны сервисные настройки: действия кнопок, модальные окна, ввод с джойстика/клавиатуры, статистика, настройки призов и маршрутизация QR.

- Переработаны gameplay HUD и overlay-панели на UI Toolkit со спрайтовыми плашками, DEV BUILD-статистикой, экраном продолжения и экраном результата приза.

- Исправлены сценарии смерти, GameOver, продолжения игры, начисления очков за высоту и перехода в главное меню.

- Перенесены UI-шаблоны из Resources в Assets/UI, подключена Addressables-загрузка шаблонов настроек и standalone-стили для MainMenuModals.

- Добавлены EditMode/PlayMode тесты для призов, QR, arcade input, UI-модалок, scene flow, HUD и gameplay-сессии.

- Обновлены сцены, настройки Addressables, service settings database, документация и дорожная карта.

Проверки:

- Targeted EditMode QR/UI/settings: 23/23 passed

- Targeted UI templates/EditMode: 17/17 passed

- MainMenuUxmlTests: 3/3 passed

- Full EditMode: 129/129 passed

- PlayMode: 6/6 passed

- Runtime Addressables template load check: loaded=5

- Unity Console после финального refresh: 0 errors
2026-06-12 16:43:28 +03:00

406 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using DanroJump.Prizes;
using DanroJump.UI.Modals;
using UnityEngine;
using UnityEngine.UIElements;
namespace DanroJump.UI.MainMenu
{
/// <summary>
/// Модальное окно добавления призового порога по очкам.
/// </summary>
public sealed class PrizeLevelAddModalWindow : ModalWindowBase, IDisposable
{
private const int ScoreStep = 10;
private readonly Label scoreLabel;
private readonly Label kindLabel;
private readonly Label statusLabel;
private readonly Button scoreMinusButton;
private readonly Button scorePlusButton;
private readonly Button kindPreviousButton;
private readonly Button kindNextButton;
private readonly Button saveButton;
private readonly Button cancelButton;
private int score = 100;
private int lastNavigationSubmitFrame = -1;
private PrizeRewardKind rewardKind = PrizeRewardKind.QRCode;
public PrizeLevelAddModalWindow(VisualElement root)
: base(root)
{
if (root == null)
{
return;
}
scoreLabel = root.Q<Label>("PrizeLevelAddScoreValue");
kindLabel = root.Q<Label>("PrizeLevelAddKindValue");
statusLabel = root.Q<Label>("PrizeLevelAddStatus");
scoreMinusButton = root.Q<Button>("PrizeLevelAddScoreMinusButton");
scorePlusButton = root.Q<Button>("PrizeLevelAddScorePlusButton");
kindPreviousButton = root.Q<Button>("PrizeLevelAddKindPreviousButton");
kindNextButton = root.Q<Button>("PrizeLevelAddKindNextButton");
saveButton = root.Q<Button>("PrizeLevelAddSaveButton");
cancelButton = root.Q<Button>("PrizeLevelAddCancelButton");
root.RegisterCallback<NavigationSubmitEvent>(HandleNavigationSubmit, TrickleDown.TrickleDown);
if (scoreMinusButton != null)
{
scoreMinusButton.clicked += HandleScoreMinusClicked;
}
if (scorePlusButton != null)
{
scorePlusButton.clicked += HandleScorePlusClicked;
}
if (kindPreviousButton != null)
{
kindPreviousButton.clicked += HandleKindPreviousClicked;
}
if (kindNextButton != null)
{
kindNextButton.clicked += HandleKindNextClicked;
}
if (saveButton != null)
{
saveButton.clicked += HandleSaveClicked;
}
if (cancelButton != null)
{
cancelButton.clicked += HandleCancelClicked;
}
}
public event Action<int, PrizeRewardKind> Saved;
public void SetInitialScore(int value)
{
score = Mathf.Max(0, value);
rewardKind = PrizeRewardKind.QRCode;
Refresh();
}
public override void Open()
{
lastNavigationSubmitFrame = -1;
Refresh();
base.Open();
}
public void Dispose()
{
if (Root != null)
{
Root.UnregisterCallback<NavigationSubmitEvent>(HandleNavigationSubmit, TrickleDown.TrickleDown);
}
if (scoreMinusButton != null)
{
scoreMinusButton.clicked -= HandleScoreMinusClicked;
}
if (scorePlusButton != null)
{
scorePlusButton.clicked -= HandleScorePlusClicked;
}
if (kindPreviousButton != null)
{
kindPreviousButton.clicked -= HandleKindPreviousClicked;
}
if (kindNextButton != null)
{
kindNextButton.clicked -= HandleKindNextClicked;
}
if (saveButton != null)
{
saveButton.clicked -= HandleSaveClicked;
}
if (cancelButton != null)
{
cancelButton.clicked -= HandleCancelClicked;
}
}
public override bool HandleNavigationMove(VisualElement current, NavigationMoveEvent.Direction direction)
{
switch (direction)
{
case NavigationMoveEvent.Direction.Left:
return AdjustFocusedValue(current, -1);
case NavigationMoveEvent.Direction.Right:
return AdjustFocusedValue(current, 1);
default:
return false;
}
}
public override bool HandleNavigationKey(VisualElement current, KeyCode keyCode)
{
switch (keyCode)
{
case KeyCode.LeftArrow:
case KeyCode.A:
return AdjustFocusedValue(current, -1);
case KeyCode.RightArrow:
case KeyCode.D:
return AdjustFocusedValue(current, 1);
case KeyCode.Return:
case KeyCode.KeypadEnter:
case KeyCode.Space:
case KeyCode.JoystickButton0:
return ActivateFocusedElement(current);
default:
return false;
}
}
protected override VisualElement GetDefaultFocusElement()
{
return scorePlusButton ?? kindNextButton ?? saveButton ?? cancelButton;
}
private void DecreaseScore()
{
score = Mathf.Max(0, score - ScoreStep);
Refresh();
}
private void IncreaseScore()
{
score = Mathf.Max(0, score + ScoreStep);
Refresh();
}
private void SelectPreviousKind()
{
rewardKind = PrizeLevelUiFormatter.PreviousKind(rewardKind);
Refresh();
}
private void SelectNextKind()
{
rewardKind = PrizeLevelUiFormatter.NextKind(rewardKind);
Refresh();
}
private void Save()
{
Saved?.Invoke(score, rewardKind);
RequestClose();
}
private void HandleNavigationSubmit(NavigationSubmitEvent evt)
{
evt.StopImmediatePropagation();
if (Time.frameCount == lastNavigationSubmitFrame)
{
return;
}
lastNavigationSubmitFrame = Time.frameCount;
ActivateFocusedElement(evt.target as VisualElement);
}
private void HandleScoreMinusClicked()
{
if (IsDuplicateNavigationSubmitClick())
{
return;
}
DecreaseScore();
}
private void HandleScorePlusClicked()
{
if (IsDuplicateNavigationSubmitClick())
{
return;
}
IncreaseScore();
}
private void HandleKindPreviousClicked()
{
if (IsDuplicateNavigationSubmitClick())
{
return;
}
SelectPreviousKind();
}
private void HandleKindNextClicked()
{
if (IsDuplicateNavigationSubmitClick())
{
return;
}
SelectNextKind();
}
private void HandleSaveClicked()
{
if (IsDuplicateNavigationSubmitClick())
{
return;
}
Save();
}
private void HandleCancelClicked()
{
if (IsDuplicateNavigationSubmitClick())
{
return;
}
RequestClose();
}
private bool AdjustFocusedValue(VisualElement current, int direction)
{
var focused = ResolveFocusedElement(current);
if (IsScoreControl(focused))
{
if (direction < 0)
{
DecreaseScore();
}
else
{
IncreaseScore();
}
return true;
}
if (IsKindControl(focused))
{
if (direction < 0)
{
SelectPreviousKind();
}
else
{
SelectNextKind();
}
return true;
}
return false;
}
private bool ActivateFocusedElement(VisualElement current)
{
var focused = ResolveFocusedElement(current);
if (focused == scoreMinusButton)
{
DecreaseScore();
return true;
}
if (focused == scorePlusButton)
{
IncreaseScore();
return true;
}
if (focused == kindPreviousButton)
{
SelectPreviousKind();
return true;
}
if (focused == kindNextButton)
{
SelectNextKind();
return true;
}
if (focused == saveButton)
{
Save();
return true;
}
if (focused == cancelButton)
{
RequestClose();
return true;
}
return false;
}
private VisualElement ResolveFocusedElement(VisualElement current)
{
return ResolveButton(current) ??
ResolveButton(Root?.panel?.focusController?.focusedElement as VisualElement) ??
LastFocusedElement ??
GetDefaultFocusElement();
}
private static Button ResolveButton(VisualElement element)
{
for (var current = element; current != null; current = current.parent)
{
if (current is Button button)
{
return button;
}
}
return null;
}
private bool IsScoreControl(VisualElement element)
{
return element == scoreMinusButton || element == scorePlusButton;
}
private bool IsKindControl(VisualElement element)
{
return element == kindPreviousButton || element == kindNextButton;
}
private bool IsDuplicateNavigationSubmitClick()
{
return Time.frameCount == lastNavigationSubmitFrame;
}
private void Refresh()
{
if (scoreLabel != null)
{
scoreLabel.text = score.ToString();
}
if (kindLabel != null)
{
kindLabel.text = PrizeLevelUiFormatter.FormatKind(rewardKind);
}
if (statusLabel != null)
{
statusLabel.text = "Если такой порог уже есть, его тип приза будет обновлен.";
}
}
}
}