using System.Collections.Generic; using System.Reflection; using UnityEngine; using Zenject; using FlappyBird.Core; namespace FlappyBird.UI { public class GameUIView : MonoBehaviour { [Header("Score Display")] [SerializeField] private Component currentScoreText; [SerializeField] private Component bestScoreText; [SerializeField] private RectTransform popupContainer; [SerializeField] private float scorePopScale = 1.4f; [SerializeField] private float scorePopDuration = 0.1f; [Header("Screens")] [SerializeField] private CanvasGroup tapToStartGroup; [SerializeField] private CanvasGroup gameOverGroup; [SerializeField] private float fadeSpeed = 5f; [Header("Game Over Details")] [SerializeField] private Component gameOverScoreText; [SerializeField] private Component gameOverBestScoreText; private IGameUIController _controller; private RectTransform _currentScoreRect; private Vector3 _currentScoreBaseScale; private float _scorePopTimer; private List _activePopups = new List(); public Transform PopupTransform => transform; public RectTransform PopupContainer => popupContainer != null ? popupContainer : (transform as RectTransform); [Inject] public void Construct(IGameUIController controller) { _controller = controller; } private void Awake() { if (currentScoreText != null) { _currentScoreRect = currentScoreText.transform as RectTransform; _currentScoreBaseScale = _currentScoreRect.localScale; } } private void Start() => _controller.Bind(this); private void Update() { UpdateScorePop(); UpdateScreenFades(); UpdateMenuPulse(); HandlePopupCleanup(); } public void ShowScoreGain() { _scorePopTimer = scorePopDuration; } public void SetCurrentScore(int score) { SetText(currentScoreText, score.ToString()); SetText(gameOverScoreText, score.ToString()); } public void SetBestScore(int bestScore) { SetText(bestScoreText, $"BEST: {bestScore}"); SetText(gameOverBestScoreText, bestScore.ToString()); } private void UpdateScorePop() { if (_scorePopTimer <= 0) return; _scorePopTimer -= Time.unscaledDeltaTime; // Use an animation curve feel: pop out fast, shrink back float t = _scorePopTimer / scorePopDuration; float scale = Mathf.Lerp(1f, scorePopScale, t); _currentScoreRect.localScale = _currentScoreBaseScale * scale; } private void UpdateScreenFades() { float step = fadeSpeed * Time.unscaledDeltaTime; // Smoothly fade groups based on their active state handled in Controller tapToStartGroup.alpha = Mathf.MoveTowards(tapToStartGroup.alpha, tapToStartGroup.interactable ? 1 : 0, step); gameOverGroup.alpha = Mathf.MoveTowards(gameOverGroup.alpha, gameOverGroup.interactable ? 1 : 0, step); } private void UpdateMenuPulse() { if (tapToStartGroup.interactable) { float pulse = 1f + Mathf.Sin(Time.unscaledTime * 4f) * 0.05f; tapToStartGroup.transform.localScale = Vector3.one * pulse; } } public void SetTapToStartVisible(bool visible) { tapToStartGroup.interactable = visible; tapToStartGroup.blocksRaycasts = visible; } public void SetGameOverVisible(bool visible) { gameOverGroup.interactable = visible; gameOverGroup.blocksRaycasts = visible; } public void SetCurrentScoreVisible(bool visible) { currentScoreText.gameObject.SetActive(visible); } private void HandlePopupCleanup() { // Logic to return expired popups to pool would go here via the Controller } private static void SetText(Component target, string value) { if (target == null) return; var prop = target.GetType().GetProperty("text"); prop?.SetValue(target, value, null); } } }