Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- доработана плашка проигрыша `LoseBannerController`: добавлены отдельные поля `Menu Button` и `Menu Button Text` для кнопки возврата в главное меню - добавлена логика переключения кнопок по настройке `gameplay.canContinueGame`: при включенной настройке показывается только кнопка продолжения, при выключенной — только кнопка возврата в меню - добавлены отдельные форматы текста для обеих кнопок: `Continue Button Text Format` и `Menu Button Text Format` с подстановкой таймера автоматического действия - реализован единый таймер автодействия для активной кнопки: обратный отсчет отображается либо на кнопке продолжения, либо на кнопке возврата в меню — в зависимости от текущего режима - улучшено управление фокусом в плашке проигрыша: баннер автоматически назначает `firstSelected` и переводит `EventSystem` только на активную видимую кнопку - обновлен показ очков в игровой витрине: подготовлены поля и связанный UI для отображения счета игрока в `Level UI` и связанных настройках - синхронизированы изменения сцены и настроек: обновлены `Level UI.unity`, `MainSettingsDatabase.asset` и связанные TMP/font assets после правок интерфейса
292 lines
10 KiB
C#
292 lines
10 KiB
C#
using System;
|
||
using System.Collections;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.EventSystems;
|
||
using TMPro;
|
||
|
||
using RR.Common.Settings;
|
||
using RR.Game2D.Core.Managers;
|
||
|
||
namespace RR.Common.UI
|
||
{
|
||
/// <summary>
|
||
/// Контроллер баннера проигрыша.
|
||
/// Модальное окно, которое отображается при поражении игрока.
|
||
/// </summary>
|
||
public class LoseBannerController : Modal
|
||
{
|
||
[Header("Элементы баннера проигрыша")]
|
||
[SerializeField] private TextMeshProUGUI loseText;
|
||
[SerializeField] private TextMeshProUGUI creditText;
|
||
[SerializeField] private Button continueButton;
|
||
[SerializeField] private TextMeshProUGUI continueButtonText;
|
||
[SerializeField] private Button menuButton;
|
||
[SerializeField] private TextMeshProUGUI menuButtonText;
|
||
[SerializeField] private float continueButtonCountdown = 30f;
|
||
|
||
[Header("Тексты")]
|
||
[SerializeField] private string creditTextFormat = "У вас осталось: {0}";
|
||
[SerializeField] private string continueButtonTextFormat = "Продолжить? ({0})";
|
||
[SerializeField] private string menuButtonTextFormat = "В меню ({0})";
|
||
|
||
private RR.Common.Managers.BalanceManager _balanceManager;
|
||
private Coroutine _continueCountdownCoroutine;
|
||
private bool _isSubscribedToBalance = false;
|
||
|
||
/// <summary>
|
||
/// Событие, вызываемое при нажатии "Продолжить" или закрытии баннера.
|
||
/// </summary>
|
||
public event Action ContinueClicked;
|
||
|
||
protected override void Awake()
|
||
{
|
||
base.Awake();
|
||
|
||
if (continueButton)
|
||
{
|
||
continueButton.onClick.AddListener(OnContinueButtonClicked);
|
||
}
|
||
if (menuButton)
|
||
{
|
||
menuButton.onClick.AddListener(OnMenuButtonClicked);
|
||
}
|
||
}
|
||
|
||
protected override void OnEnable()
|
||
{
|
||
base.OnEnable();
|
||
|
||
// Инициализируем BalanceManager
|
||
if (_balanceManager == null)
|
||
{
|
||
_balanceManager = GameManager.Instance?.Balance;
|
||
}
|
||
|
||
// Подписываемся на событие только если еще не подписаны
|
||
if (_balanceManager != null && !_isSubscribedToBalance)
|
||
{
|
||
int balance = _balanceManager.CurrentBalance;
|
||
if (creditText)
|
||
{
|
||
creditText.text = string.Format(creditTextFormat, balance);
|
||
}
|
||
_balanceManager.OnBalanceChanged.AddListener(UnlockContinueButton);
|
||
_isSubscribedToBalance = true;
|
||
}
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
// Остановим корутину, если объект деактивируется
|
||
if (_continueCountdownCoroutine != null)
|
||
{
|
||
StopCoroutine(_continueCountdownCoroutine);
|
||
_continueCountdownCoroutine = null;
|
||
}
|
||
|
||
// Безопасная отписка от события
|
||
if (_balanceManager != null && _isSubscribedToBalance)
|
||
{
|
||
_balanceManager.OnBalanceChanged.RemoveListener(UnlockContinueButton);
|
||
_isSubscribedToBalance = false;
|
||
}
|
||
|
||
base.OnDisable();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Показывает баннер проигрыша с указанным временем игры.
|
||
/// </summary>
|
||
/// <param name="gameTime">Время игры в секундах</param>
|
||
public void ShowLose(float gameTime)
|
||
{
|
||
// Инициализируем BalanceManager до открытия окна
|
||
if (_balanceManager == null)
|
||
{
|
||
_balanceManager = GameManager.Instance?.Balance;
|
||
}
|
||
|
||
// Подписываемся на событие изменения баланса, если еще не подписаны
|
||
if (_balanceManager != null && !_isSubscribedToBalance)
|
||
{
|
||
_balanceManager.OnBalanceChanged.AddListener(UnlockContinueButton);
|
||
_isSubscribedToBalance = true;
|
||
}
|
||
|
||
if (loseText)
|
||
{
|
||
loseText.text = $"Время игры: {TimeFormatHelper.FormatTime(gameTime)}";
|
||
}
|
||
|
||
if (creditText && _balanceManager != null)
|
||
{
|
||
creditText.text = string.Format(creditTextFormat, (object)_balanceManager.CurrentBalance);
|
||
}
|
||
|
||
if (continueButtonCountdown <= 0f)
|
||
{
|
||
ReturnToMainMenu();
|
||
return;
|
||
}
|
||
|
||
Button activeButton = ConfigureVisibleButtons();
|
||
if (activeButton != null)
|
||
{
|
||
SetFirstSelected(activeButton);
|
||
}
|
||
|
||
Open();
|
||
FocusActiveButton(activeButton);
|
||
|
||
// Остановим предыдущую корутину, если есть
|
||
if (_continueCountdownCoroutine != null)
|
||
{
|
||
StopCoroutine(_continueCountdownCoroutine);
|
||
}
|
||
|
||
_continueCountdownCoroutine = StartCoroutine(ContinueCountdownCoroutine(continueButtonCountdown));
|
||
}
|
||
|
||
private IEnumerator ContinueCountdownCoroutine(float duration)
|
||
{
|
||
float elapsed = 0f;
|
||
int initialSeconds = Mathf.RoundToInt(duration);
|
||
UpdateContinueText(initialSeconds);
|
||
|
||
while (elapsed < duration)
|
||
{
|
||
float startTime = Time.unscaledTime;
|
||
yield return null;
|
||
|
||
while (Time.unscaledTime - startTime < 1.0f)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
elapsed += 1f;
|
||
int remaining = Mathf.Max(0, Mathf.RoundToInt(duration - elapsed));
|
||
UpdateContinueText(remaining);
|
||
}
|
||
|
||
// Таймер истёк — закрываем баннер и выходим в меню
|
||
Close();
|
||
ContinueClicked?.Invoke();
|
||
|
||
if (GameManager.Instance)
|
||
{
|
||
GameManager.Instance.SetPause(false);
|
||
GameManager.Instance.LoadScene("MainMenu", false);
|
||
}
|
||
|
||
_continueCountdownCoroutine = null;
|
||
}
|
||
|
||
private void UpdateContinueText(int secondsRemaining)
|
||
{
|
||
if (continueButton != null && continueButton.gameObject.activeSelf && continueButtonText)
|
||
{
|
||
continueButtonText.text = string.Format(continueButtonTextFormat, secondsRemaining);
|
||
}
|
||
|
||
if (menuButton != null && menuButton.gameObject.activeSelf && menuButtonText)
|
||
{
|
||
menuButtonText.text = string.Format(menuButtonTextFormat, secondsRemaining);
|
||
}
|
||
}
|
||
|
||
private void UnlockContinueButton(int newBalance)
|
||
{
|
||
if (_balanceManager == null) return;
|
||
|
||
// Проверяем возможность продолжить через BalanceManager
|
||
if (continueButton)
|
||
{
|
||
continueButton.interactable = _balanceManager.CanContinueGame();
|
||
}
|
||
|
||
// Обновляем текст баланса
|
||
if (creditText)
|
||
{
|
||
creditText.text = string.Format(creditTextFormat, newBalance);
|
||
}
|
||
}
|
||
|
||
private void OnContinueButtonClicked()
|
||
{
|
||
// При клике на "Продолжить" — остановим таймер и продолжим игру
|
||
if (_continueCountdownCoroutine != null)
|
||
{
|
||
StopCoroutine(_continueCountdownCoroutine);
|
||
_continueCountdownCoroutine = null;
|
||
}
|
||
|
||
Close();
|
||
ContinueClicked?.Invoke();
|
||
|
||
if (GameManager.Instance)
|
||
{
|
||
GameManager.Instance.SetPause(false);
|
||
GameManager.Instance.ContinueGame();
|
||
}
|
||
}
|
||
|
||
private void OnMenuButtonClicked()
|
||
{
|
||
if (_continueCountdownCoroutine != null)
|
||
{
|
||
StopCoroutine(_continueCountdownCoroutine);
|
||
_continueCountdownCoroutine = null;
|
||
}
|
||
|
||
ReturnToMainMenu();
|
||
}
|
||
|
||
private Button ConfigureVisibleButtons()
|
||
{
|
||
bool canContinueGameSetting = SettingsManager.Instance != null
|
||
&& SettingsManager.Instance.GetBool("gameplay.canContinueGame");
|
||
|
||
if (continueButton != null)
|
||
{
|
||
continueButton.gameObject.SetActive(canContinueGameSetting);
|
||
continueButton.interactable = canContinueGameSetting
|
||
&& _balanceManager != null
|
||
&& _balanceManager.CanContinueGame();
|
||
}
|
||
|
||
if (menuButton != null)
|
||
{
|
||
menuButton.gameObject.SetActive(!canContinueGameSetting);
|
||
menuButton.interactable = true;
|
||
}
|
||
|
||
return canContinueGameSetting ? continueButton : menuButton;
|
||
}
|
||
|
||
private void FocusActiveButton(Button activeButton)
|
||
{
|
||
if (activeButton == null || !activeButton.gameObject.activeInHierarchy || EventSystem.current == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
EventSystem.current.SetSelectedGameObject(null);
|
||
EventSystem.current.SetSelectedGameObject(activeButton.gameObject);
|
||
activeButton.Select();
|
||
}
|
||
|
||
private void ReturnToMainMenu()
|
||
{
|
||
Close();
|
||
ContinueClicked?.Invoke();
|
||
|
||
if (GameManager.Instance)
|
||
{
|
||
GameManager.Instance.SetPause(false);
|
||
GameManager.Instance.LoadScene("MainMenu", false);
|
||
}
|
||
}
|
||
}
|
||
}
|