using System.Threading;
using Cysharp.Threading.Tasks;
using DanroJump.Gameplay;
using UnityEngine;
using UnityEngine.UIElements;
using Zenject;
namespace DanroJump.UI.Gameplay
{
///
/// Отображает runtime-показатели игрового забега в UI Toolkit HUD.
///
public sealed class GameplayHudController : MonoBehaviour
{
[SerializeField] private VisualTreeAsset visualTreeAsset;
[SerializeField] private PanelSettings panelSettings;
[SerializeField] private int sortingOrder = 10;
[SerializeField] private string scoreLabelName = "ScoreValue";
[SerializeField] private string heightLabelName = "HeightValue";
[SerializeField] private string bestHeightLabelName = "BestHeightValue";
[SerializeField] private string livesLabelName = "LivesValue";
[SerializeField] private string coinsLabelName = "CoinsValue";
[SerializeField] private string deathsLabelName = "DeathsValue";
[SerializeField] private string stateLabelName = "StateValue";
private UIDocument document;
private GameplaySession session;
private Label scoreLabel;
private Label heightLabel;
private Label bestHeightLabel;
private Label livesLabel;
private Label coinsLabel;
private Label deathsLabel;
private Label stateLabel;
private CancellationTokenSource refreshCancellation;
private bool isSubscribedToSession;
///
/// Получает игровую сессию из DI-контейнера сцены.
///
[Inject]
public void Construct(GameplaySession injectedSession)
{
SetSession(injectedSession);
}
private void Start()
{
EnsureDocument();
BindLabels();
Refresh();
}
///
/// Подписывается на изменения сессии и запускает кадровую подстраховку обновления HUD.
///
private void OnEnable()
{
SubscribeToSession();
if (refreshCancellation == null)
{
refreshCancellation = new CancellationTokenSource();
RefreshContinuouslyAsync(refreshCancellation.Token).Forget(Debug.LogException);
}
Refresh();
}
///
/// Останавливает обновление HUD и снимает подписку с игровой сессии.
///
private void OnDisable()
{
if (refreshCancellation != null)
{
refreshCancellation.Cancel();
refreshCancellation.Dispose();
refreshCancellation = null;
}
UnsubscribeFromSession();
}
///
/// Создает или настраивает UIDocument, чтобы HUD мог жить на отдельном GameObject без ручной сборки.
///
private void EnsureDocument()
{
if (document == null && !TryGetComponent(out document))
{
document = gameObject.AddComponent();
}
if (document == null)
{
return;
}
document.visualTreeAsset = visualTreeAsset;
document.panelSettings = panelSettings;
document.sortingOrder = sortingOrder;
}
///
/// Находит текстовые поля в UXML по именам, заданным в инспекторе.
///
private void BindLabels()
{
var root = document != null ? document.rootVisualElement : null;
if (root == null)
{
return;
}
scoreLabel = FindLabel(root, scoreLabelName);
heightLabel = FindLabel(root, heightLabelName);
bestHeightLabel = FindLabel(root, bestHeightLabelName);
livesLabel = FindLabel(root, livesLabelName);
coinsLabel = FindLabel(root, coinsLabelName);
deathsLabel = FindLabel(root, deathsLabelName);
stateLabel = FindLabel(root, stateLabelName);
}
///
/// Обновляет все значения HUD из текущей игровой сессии.
///
private void Refresh()
{
if (session == null)
{
SetLabel(stateLabel, "WAITING");
return;
}
SetLabel(scoreLabel, session.Score.ToString());
SetLabel(heightLabel, $"{session.MaxRunHeight:0} м");
SetLabel(bestHeightLabel, $"{session.RecordHeight:0} м");
SetLabel(livesLabel, session.RemainingLives.ToString());
SetLabel(coinsLabel, session.Coins.ToString());
SetLabel(deathsLabel, session.DeathCount.ToString());
SetLabel(stateLabel, FormatState(session.State));
}
///
/// Безопасно обновляет текст label, если элемент найден в UXML.
///
private static void SetLabel(Label label, string value)
{
if (label != null)
{
label.text = value;
}
}
///
/// Ищет Label по имени, заданному в настройках контроллера.
///
private static Label FindLabel(VisualElement root, string labelName)
{
return root != null && !string.IsNullOrEmpty(labelName)
? root.Q