[Add] UI, menu, pause and timer

This commit is contained in:
2026-06-06 22:03:20 +07:00
parent 1483964eaf
commit f4ecf8b6f9
23 changed files with 2440 additions and 11 deletions
@@ -0,0 +1,289 @@
using System;
using System.Collections.Generic;
using Minesweeper.Core;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Minesweeper.Presentation.Views
{
public sealed class GameView : MonoBehaviour, IGameView
{
[SerializeField] private GameObject gameRoot;
[SerializeField] private GameObject pauseRoot;
[SerializeField] private RectTransform boardPanel;
[SerializeField] private GridLayoutGroup gridLayoutGroup;
[SerializeField] private Button pauseButton;
[SerializeField] private Button restartButton;
[SerializeField] private Button resumeButton;
[SerializeField] private Button mainMenuButton;
[SerializeField] private TMP_Text timerText;
[SerializeField] private TMP_Text mineText;
[SerializeField] private float spacing = 2f;
[SerializeField] private float basePixelsPerUnitCellSize = 32f;
private readonly Dictionary<int, CellView> cellsByCoordinate = new Dictionary<int, CellView>();
private bool boardInputEnabled = true;
private float currentPixelsPerUnitMultiplier = 1f;
public event Action RestartRequested;
public event Action GoToMenuRequested;
public event Action PauseRequested;
public event Action ResumeRequested;
public event Action<int, int> CellOpenRequested;
public event Action<int, int> CellFlagRequested;
private void Awake()
{
if (gameRoot == null)
{
gameRoot = gameObject;
}
}
private void OnEnable()
{
AddButtonListeners();
}
private void OnDisable()
{
RemoveButtonListeners();
}
public void ShowGame()
{
gameRoot.SetActive(true);
}
public void HideGame()
{
gameRoot.SetActive(false);
}
public void ShowPause()
{
if (pauseRoot != null)
{
pauseRoot.SetActive(true);
}
}
public void HidePause()
{
if (pauseRoot != null)
{
pauseRoot.SetActive(false);
}
}
public void SetTimer(float seconds)
{
if (timerText != null)
{
timerText.text = Mathf.FloorToInt(seconds).ToString("000");
}
}
public void SetMineCount(int minesCount)
{
if (mineText != null)
{
mineText.text = minesCount.ToString("000");
}
}
public void RebuildBoard(IReadOnlyList<BoardCellData> cells, int width, int height)
{
ClearBoard();
ConfigureGrid(width, height);
for (var i = 0; i < cells.Count; i++)
{
CreateCell(cells[i]);
}
RefreshBoard(cells);
}
public void RefreshBoard(IReadOnlyList<BoardCellData> cells)
{
for (var i = 0; i < cells.Count; i++)
{
var cell = cells[i];
if (cellsByCoordinate.TryGetValue(ToKey(cell.X, cell.Y), out var view))
{
view.Render(cell, currentPixelsPerUnitMultiplier);
}
}
}
public void SetBoardInputEnabled(bool enabled)
{
boardInputEnabled = enabled;
foreach (var cell in cellsByCoordinate.Values)
{
cell.SetInputEnabled(enabled);
}
}
private void AddButtonListeners()
{
if (pauseButton != null)
{
pauseButton.onClick.AddListener(OnPauseClicked);
}
if (restartButton != null)
{
restartButton.onClick.AddListener(OnRestartClicked);
}
if (resumeButton != null)
{
resumeButton.onClick.AddListener(OnResumeClicked);
}
if (mainMenuButton != null)
{
mainMenuButton.onClick.AddListener(OnMainMenuClicked);
}
}
private void RemoveButtonListeners()
{
if (pauseButton != null)
{
pauseButton.onClick.RemoveListener(OnPauseClicked);
}
if (restartButton != null)
{
restartButton.onClick.RemoveListener(OnRestartClicked);
}
if (resumeButton != null)
{
resumeButton.onClick.RemoveListener(OnResumeClicked);
}
if (mainMenuButton != null)
{
mainMenuButton.onClick.RemoveListener(OnMainMenuClicked);
}
}
private void ConfigureGrid(int width, int height)
{
if (gridLayoutGroup == null || boardPanel == null)
{
return;
}
Canvas.ForceUpdateCanvases();
var rect = boardPanel.rect;
var panelWidth = rect.width > 0f ? rect.width : 512f;
var panelHeight = rect.height > 0f ? rect.height : 512f;
var padding = gridLayoutGroup.padding;
var availableWidth = panelWidth - padding.left - padding.right - spacing * Mathf.Max(0, width - 1);
var availableHeight = panelHeight - padding.top - padding.bottom - spacing * Mathf.Max(0, height - 1);
var cellSize = Mathf.Floor(Mathf.Min(availableWidth / width, availableHeight / height));
cellSize = Mathf.Max(8f, cellSize);
gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
gridLayoutGroup.constraintCount = width;
gridLayoutGroup.spacing = new Vector2(spacing, spacing);
gridLayoutGroup.cellSize = new Vector2(cellSize, cellSize);
currentPixelsPerUnitMultiplier = Mathf.Clamp(basePixelsPerUnitCellSize / cellSize, 0.25f, 4f);
}
private void CreateCell(BoardCellData cell)
{
var go = new GameObject($"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
go.transform.SetParent(gridLayoutGroup.transform, false);
var labelGo = new GameObject("Text", typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
labelGo.transform.SetParent(go.transform, false);
var labelRect = (RectTransform)labelGo.transform;
labelRect.anchorMin = Vector2.zero;
labelRect.anchorMax = Vector2.one;
labelRect.offsetMin = Vector2.zero;
labelRect.offsetMax = Vector2.zero;
var label = labelGo.GetComponent<TextMeshProUGUI>();
label.alignment = TextAlignmentOptions.Center;
label.enableAutoSizing = true;
label.fontSizeMin = 6f;
label.fontSizeMax = 32f;
label.color = Color.black;
var view = go.AddComponent<CellView>();
view.Bind(go.GetComponent<Button>(), go.GetComponent<Image>(), label);
view.Initialize(cell.X, cell.Y);
view.SetInputEnabled(boardInputEnabled);
view.OpenRequested += OnCellOpenRequested;
view.FlagRequested += OnCellFlagRequested;
cellsByCoordinate[ToKey(cell.X, cell.Y)] = view;
}
private void ClearBoard()
{
foreach (var cell in cellsByCoordinate.Values)
{
if (cell != null)
{
cell.OpenRequested -= OnCellOpenRequested;
cell.FlagRequested -= OnCellFlagRequested;
}
}
cellsByCoordinate.Clear();
if (gridLayoutGroup == null)
{
return;
}
for (var i = gridLayoutGroup.transform.childCount - 1; i >= 0; i--)
{
Destroy(gridLayoutGroup.transform.GetChild(i).gameObject);
}
}
private void OnCellOpenRequested(int x, int y)
{
CellOpenRequested?.Invoke(x, y);
}
private void OnCellFlagRequested(int x, int y)
{
CellFlagRequested?.Invoke(x, y);
}
private void OnPauseClicked()
{
PauseRequested?.Invoke();
}
private void OnRestartClicked()
{
RestartRequested?.Invoke();
}
private void OnResumeClicked()
{
ResumeRequested?.Invoke();
}
private void OnMainMenuClicked()
{
GoToMenuRequested?.Invoke();
}
private static int ToKey(int x, int y)
{
return (y << 16) ^ x;
}
}
}