423 lines
15 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.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
/// <summary>
/// Отвечает за отображение панели GameOver и анимацию кнопок
/// </summary>
public class GameOverUI : MonoBehaviour
{
[SerializeField] private int playerId;
[SerializeField] private GameManager gameManager;
[Header("UI Elements")]
[SerializeField] private Button replayButton;
[SerializeField] private Button continueButton;
[SerializeField] private Button menuButton; // Кнопка меню
[SerializeField] private GameOver gameOverStatusImage; // Надпись победы/поражения (GameOver на firstImage)
[SerializeField] private GameOver panelBackground; // Фон панели (GameOver на самой панели)
[SerializeField] private GameOver medalSwitcher; // Медальон (GameOver на secondImage)
[Header("Animation Settings")]
[SerializeField] private Image firstImage; // Картинка надписи победы/поражения
[SerializeField] private Image secondImage; // Медальон
[SerializeField] private float popDuration = 0.3f; // Длительность "попапа" в секундах
[SerializeField] private float popScale = 1.5f; // Максимальный масштаб при "попапе"
[SerializeField] private float delayBetweenElements = 0.15f; // Задержка между появлением элементов
[SerializeField] private float buttonSlideDuration = 0.25f; // Длительность слайда кнопки
[SerializeField] private float buttonSlideOffset = 500f; // Смещение по позиции (начальное)
[SerializeField] private float pulseScale = 1.06f; // Насколько увеличивается надпись при пульсации
[SerializeField] private float pulseDuration = 1.4f; // Длительность одного цикла пульсации
private Coroutine showAnimationCoroutine;
private Coroutine pulseCoroutine;
private Vector2 replayButtonOriginalPos;
private Vector2 continueButtonOriginalPos;
private Vector2 menuButtonOriginalPos;
private bool positionsCached;
private bool eventsSubscribed;
// CanvasGroup для "прозрачности" кнопок (не меняем activeSelf)
private CanvasGroup replayCg;
private CanvasGroup continueCg;
private CanvasGroup menuCg;
// Для сохранения interactable в UpdateButtons между вызовами метода
private bool lastIsBotGame;
private void OnEnable()
{
ComWrapper.OnInitialize += Subscribe;
if (!eventsSubscribed && (ComWrapper.onCoinAdded != null || ComWrapper.onCoinRemoved != null))
Subscribe();
EnsureCanvasGroups();
}
private void OnDisable()
{
if (eventsSubscribed)
{
if (ComWrapper.onCoinAdded != null) ComWrapper.onCoinAdded.RemoveListener(HandleCoinsChanged);
if (ComWrapper.onCoinRemoved != null) ComWrapper.onCoinRemoved.RemoveListener(HandleCoinsChanged);
eventsSubscribed = false;
}
ComWrapper.OnInitialize -= Subscribe;
}
private void EnsureCanvasGroups()
{
replayCg = EnsureCanvasGroup(replayButton);
continueCg = EnsureCanvasGroup(continueButton);
menuCg = EnsureCanvasGroup(menuButton);
}
private static CanvasGroup EnsureCanvasGroup(Button b)
{
if (b == null) return null;
var cg = b.GetComponent<CanvasGroup>();
if (cg == null) cg = b.gameObject.AddComponent<CanvasGroup>();
return cg;
}
private void Subscribe()
{
if (eventsSubscribed) return;
if (ComWrapper.onCoinAdded != null) ComWrapper.onCoinAdded.AddListener(HandleCoinsChanged);
if (ComWrapper.onCoinRemoved != null) ComWrapper.onCoinRemoved.AddListener(HandleCoinsChanged);
eventsSubscribed = true;
}
private void HandleCoinsChanged(int playerID, int newAmount)
{
// Обновляем кнопки только если изменились монеты именно этого игрока или его оппонента
int oponentId = playerId == 1 ? 2 : 1;
if (playerID == playerId || playerID == oponentId)
{
UpdateButtons();
}
}
public void Show(bool isWin, bool isBotGame)
{
lastIsBotGame = isBotGame;
// Останавливаем старую анимацию если она ещё идёт
if (showAnimationCoroutine != null)
{
StopCoroutine(showAnimationCoroutine);
}
if (pulseCoroutine != null)
{
StopCoroutine(pulseCoroutine);
pulseCoroutine = null;
}
EnsureCanvasGroups();
// 1) Сразу определяем, какие кнопки видимые (чтобы список не менялся)
ApplyButtonVisibility(isWin, isBotGame);
// 2) Переключаем спрайты: фон панели, надпись, медальон (GameOver на secondImage)
if (panelBackground != null)
panelBackground.GameOverSwitch(isWin);
if (gameOverStatusImage != null)
gameOverStatusImage.GameOverSwitch(isWin);
if (medalSwitcher != null)
medalSwitcher.GameOverSwitch(isWin);
// 3) Сразу обновляем interactable (зависит от монеток)
UpdateButtons();
// 4) Чтобы сработал метод, что нужно кнопки обновлять
if (gameManager != null && gameManager.uiManager != null)
gameManager.uiManager.OnPanelChanged?.Invoke();
// 5) Готовим всё визуально перед стартом анимации и запускаем анимацию
PrepVisualsForAnimation();
int credits = ComWrapper.GetPlayerCredits(playerId);
showAnimationCoroutine = StartCoroutine(ShowElementsSequence(isWin, isBotGame, credits));
}
private void ApplyButtonVisibility(bool isWin, bool isBotGame)
{
// - Если победа в PvE: только continueButton
// - Если поражение в PvE или PvP: replayButton
// - Menu: всегда
bool showContinueButton = isBotGame && isWin;
bool showReplayButton = !showContinueButton;
if (replayButton != null) replayButton.gameObject.SetActive(showReplayButton);
if (continueButton != null) continueButton.gameObject.SetActive(showContinueButton);
if (menuButton != null) menuButton.gameObject.SetActive(true);
}
/// <summary>
/// Подготовка всех визуальных элементов: делаем начально всё с alpha=0 и смещен вниз,
/// при этом activeSelf не меняем.
/// </summary>
private void PrepVisualsForAnimation()
{
CacheButtonPositionsIfNeeded();
// Картинки: scale=0
if (firstImage != null) firstImage.transform.localScale = Vector3.zero;
if (secondImage != null) secondImage.transform.localScale = Vector3.zero;
// Кнопки: alpha=0 и сдвиг вниз (только если активны)
PrepButtonVisual(replayButton, replayCg, replayButtonOriginalPos);
PrepButtonVisual(continueButton, continueCg, continueButtonOriginalPos);
PrepButtonVisual(menuButton, menuCg, menuButtonOriginalPos);
}
private void CacheButtonPositionsIfNeeded()
{
if (positionsCached) return;
if (replayButton != null)
{
var rt = replayButton.GetComponent<RectTransform>();
if (rt != null) replayButtonOriginalPos = rt.anchoredPosition;
}
if (continueButton != null)
{
var rt = continueButton.GetComponent<RectTransform>();
if (rt != null) continueButtonOriginalPos = rt.anchoredPosition;
}
if (menuButton != null)
{
var rt = menuButton.GetComponent<RectTransform>();
if (rt != null) menuButtonOriginalPos = rt.anchoredPosition;
}
positionsCached = true;
}
private void PrepButtonVisual(Button b, CanvasGroup cg, Vector2 original)
{
if (b == null || cg == null) return;
if (!b.gameObject.activeSelf) return;
cg.alpha = 0f;
var rt = b.GetComponent<RectTransform>();
if (rt != null)
rt.anchoredPosition = new Vector2(original.x, original.y - buttonSlideOffset);
}
private IEnumerator ShowElementsSequence(bool isWin, bool isBotGame, int credits)
{
// 1. Первая картинка
if (firstImage != null)
{
yield return StartCoroutine(PopInElement(firstImage.transform));
yield return new WaitForSeconds(delayBetweenElements);
}
// 2. Вторая картинка
if (secondImage != null)
{
yield return StartCoroutine(PopInElement(secondImage.transform));
yield return new WaitForSeconds(delayBetweenElements);
}
// 3. Кнопки действия
if (isBotGame)
yield return StartCoroutine(ShowBotGameButtonsAnimated(isWin, credits));
else
yield return StartCoroutine(ShowPvPGameButtonsAnimated(credits));
// 4. Меню
yield return StartCoroutine(ShowMenuButtonAnimated());
// 5. Пульсация надписи победы/поражения
if (firstImage != null)
pulseCoroutine = StartCoroutine(PulseLoop(firstImage.transform));
}
private IEnumerator PulseLoop(Transform element)
{
float half = pulseDuration * 0.5f;
while (element != null)
{
// Плавно увеличиваем
float elapsed = 0f;
while (elapsed < half)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / half);
element.localScale = Vector3.one * Mathf.Lerp(1f, pulseScale, t);
yield return null;
}
// Плавно уменьшаем
elapsed = 0f;
while (elapsed < half)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / half);
element.localScale = Vector3.one * Mathf.Lerp(pulseScale, 1f, t);
yield return null;
}
}
}
private IEnumerator PopInElement(Transform element)
{
float elapsed = 0f;
Vector3 targetScale = Vector3.one;
while (elapsed < popDuration)
{
elapsed += Time.deltaTime;
float progress = elapsed / popDuration;
float scale;
if (progress < 0.4f)
{
scale = Mathf.Lerp(0f, popScale, progress / 0.4f);
}
else
{
float returnProgress = (progress - 0.4f) / 0.6f;
scale = Mathf.Lerp(popScale, 1f, returnProgress);
}
element.localScale = Vector3.one * scale;
yield return null;
}
element.localScale = targetScale;
}
private IEnumerator SlideInButton(RectTransform rectTransform, Vector2 targetPosition)
{
Vector2 startPosition = rectTransform.anchoredPosition;
float elapsed = 0f;
while (elapsed < buttonSlideDuration)
{
elapsed += Time.deltaTime;
float progress = elapsed / buttonSlideDuration;
float easedProgress = 1f - Mathf.Pow(1f - progress, 3f);
rectTransform.anchoredPosition = Vector2.Lerp(startPosition, targetPosition, easedProgress);
yield return null;
}
rectTransform.anchoredPosition = targetPosition;
}
private IEnumerator FadeIn(CanvasGroup cg)
{
if (cg == null) yield break;
float elapsed = 0f;
while (elapsed < buttonSlideDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / buttonSlideDuration);
cg.alpha = t;
yield return null;
}
cg.alpha = 1f;
}
private IEnumerator SlideAndFadeIn(Button b, CanvasGroup cg, Vector2 targetPos)
{
if (b == null || cg == null) yield break;
if (!b.gameObject.activeSelf) yield break;
var rt = b.GetComponent<RectTransform>();
if (rt == null)
{
cg.alpha = 1f;
yield break;
}
// Одновременно: slide + fade
Coroutine slide = StartCoroutine(SlideInButton(rt, targetPos));
Coroutine fade = StartCoroutine(FadeIn(cg));
yield return slide;
yield return fade;
}
private IEnumerator ShowBotGameButtonsAnimated(bool isWin, int credits)
{
if (isWin)
{
if (continueButton != null && continueButton.gameObject.activeSelf)
{
continueButton.interactable = true;
yield return StartCoroutine(SlideAndFadeIn(continueButton, continueCg, continueButtonOriginalPos));
}
}
else
{
if (replayButton != null && replayButton.gameObject.activeSelf)
{
replayButton.interactable = credits >= 1;
var txt = replayButton.GetComponentInChildren<TMP_Text>();
if (txt != null) txt.text = "Реванш!";
yield return StartCoroutine(SlideAndFadeIn(replayButton, replayCg, replayButtonOriginalPos));
}
}
}
private IEnumerator ShowPvPGameButtonsAnimated(int credits)
{
if (replayButton != null && replayButton.gameObject.activeSelf)
{
int oponentId = playerId == 1 ? 2 : 1;
int oponentCredits = ComWrapper.GetPlayerCredits(oponentId);
replayButton.interactable = credits >= 2 || (credits >= 1 && oponentCredits >= 1);
var txt = replayButton.GetComponentInChildren<TMP_Text>();
if (txt != null) txt.text = "Еще раз!";
yield return StartCoroutine(SlideAndFadeIn(replayButton, replayCg, replayButtonOriginalPos));
}
}
private IEnumerator ShowMenuButtonAnimated()
{
if (menuButton != null && menuButton.gameObject.activeSelf)
{
// Кнопка меню всегда интерактивна
yield return StartCoroutine(SlideAndFadeIn(menuButton, menuCg, menuButtonOriginalPos));
}
}
public void UpdateButtons()
{
int credits = ComWrapper.GetPlayerCredits(playerId);
int oponentId = playerId == 1 ? 2 : 1;
int oponentCredits = ComWrapper.GetPlayerCredits(oponentId);
if (continueButton != null && continueButton.gameObject.activeSelf)
continueButton.interactable = credits >= 1;
if (replayButton != null && replayButton.gameObject.activeSelf)
{
if (lastIsBotGame)
replayButton.interactable = credits >= 1;
else
replayButton.interactable = credits >= 2 || (credits >= 1 && oponentCredits >= 1);
}
}
}