[Fix] Board Optimization, Fix Screen Loading

This commit is contained in:
2026-06-07 00:57:20 +07:00
parent e487795e6f
commit 285c11597a
9 changed files with 117 additions and 94 deletions
@@ -1,4 +1,3 @@
using Minesweeper.Config;
using Minesweeper.Presentation.Views;
using UnityEngine;
@@ -11,7 +10,12 @@ namespace Minesweeper.Presentation.Factories
private const string PausePanelName = "PausePanel";
private const string ResultPanelName = "ResultPanel";
public MinesweeperScreenRefs Spawn(Transform contentRoot, MinesweeperScreenCatalog catalog)
public MinesweeperScreenRefs Spawn(
Transform contentRoot,
MainMenuView mainMenuPrefab,
BoardView boardPrefab,
PauseView pausePrefab,
ResultView resultPrefab)
{
if (contentRoot == null)
{
@@ -19,47 +23,42 @@ namespace Minesweeper.Presentation.Factories
return default;
}
if (catalog == null || !catalog.IsValid)
if (mainMenuPrefab == null || boardPrefab == null || pausePrefab == null || resultPrefab == null)
{
Debug.LogError("Minesweeper screen bootstrap failed: screen catalog prefab references are incomplete.");
Debug.LogError("Minesweeper screen bootstrap failed: screen prefab references are incomplete.");
return default;
}
ClearContent(contentRoot);
var mainMenu = SpawnScreen(catalog.MainMenuPanelPrefab, contentRoot, MainMenuPanelName, 0);
var board = SpawnScreen(catalog.BoardGridPrefab, contentRoot, BoardGridName, 1);
var pause = SpawnScreen(catalog.PausePanelPrefab, contentRoot, PausePanelName, 2);
var result = SpawnScreen(catalog.ResultPanelPrefab, contentRoot, ResultPanelName, 3);
var mainMenuView = SpawnScreen(mainMenuPrefab, contentRoot, MainMenuPanelName, 0);
var boardView = SpawnScreen(boardPrefab, contentRoot, BoardGridName, 1);
var pauseView = SpawnScreen(pausePrefab, contentRoot, PausePanelName, 2);
var resultView = SpawnScreen(resultPrefab, contentRoot, ResultPanelName, 3);
var mainMenuView = RequireComponent<MainMenuView>(mainMenu.transform, MainMenuPanelName);
if (mainMenuView != null)
{
mainMenuView.BindRoot(mainMenu);
mainMenuView.BindRoot(mainMenuView.gameObject);
}
var boardView = RequireComponent<BoardView>(board.transform, BoardGridName);
var pauseView = RequireComponent<PauseView>(pause.transform, PausePanelName);
var resultView = RequireComponent<ResultView>(result.transform, ResultPanelName);
var refs = new MinesweeperScreenRefs(
mainMenuView,
boardView,
pauseView,
resultView);
mainMenu.SetActive(false);
board.SetActive(false);
pause.SetActive(false);
result.SetActive(false);
mainMenuView.gameObject.SetActive(false);
boardView.gameObject.SetActive(false);
pauseView.gameObject.SetActive(false);
resultView.gameObject.SetActive(false);
return refs;
}
private static GameObject SpawnScreen(GameObject prefab, Transform parent, string expectedName, int siblingIndex)
private static T SpawnScreen<T>(T prefab, Transform parent, string expectedName, int siblingIndex) where T : Component
{
var instance = Object.Instantiate(prefab, parent, false);
instance.name = expectedName;
instance.gameObject.name = expectedName;
instance.transform.SetSiblingIndex(siblingIndex);
Stretch(instance.GetComponent<RectTransform>());
return instance;
@@ -75,17 +74,6 @@ namespace Minesweeper.Presentation.Factories
}
}
private static T RequireComponent<T>(Transform root, string ownerName) where T : Component
{
var component = root.GetComponent<T>();
if (component == null)
{
Debug.LogError($"Minesweeper screen bootstrap failed: '{ownerName}' is missing {typeof(T).Name}.");
}
return component;
}
private static void Stretch(RectTransform rectTransform)
{
if (rectTransform == null)
+50 -15
View File
@@ -23,6 +23,7 @@ namespace Minesweeper.Presentation.Views
[SerializeField] private MinesweeperUiConfig uiConfig;
private readonly Dictionary<int, CellView> cellsByCoordinate = new Dictionary<int, CellView>();
private readonly Stack<CellView> pooledCells = new Stack<CellView>();
private IReadOnlyList<BoardCellData> currentCells;
private bool inputEnabled = true;
private bool currentRevealUnflaggedMines;
@@ -83,14 +84,27 @@ namespace Minesweeper.Presentation.Views
currentBoardHeight = height;
currentCells = cells;
currentRevealUnflaggedMines = revealUnflaggedMines;
ConfigureGrid(width, height);
var layoutWasEnabled = gridLayoutGroup != null && gridLayoutGroup.enabled;
SetGridLayoutEnabled(false);
for (var i = 0; i < cells.Count; i++)
try
{
CreateCell(cells[i], cellViewFactory);
}
ConfigureGrid(width, height);
Refresh(cells, revealUnflaggedMines);
for (var i = 0; i < cells.Count; i++)
{
var cell = cells[i];
var view = CreateCell(cell, cellViewFactory);
if (view != null)
{
view.Render(cell, uiConfig, currentPixelsPerUnitMultiplier, currentContentPadding, revealUnflaggedMines);
}
}
}
finally
{
SetGridLayoutEnabled(layoutWasEnabled);
}
}
public void Refresh(IReadOnlyList<BoardCellData> cells, bool revealUnflaggedMines)
@@ -207,17 +221,17 @@ namespace Minesweeper.Presentation.Views
return Mathf.Max(uiConfig.MinimumCellSize, Mathf.Floor(Mathf.Min(cellByWidth, cellByHeight)));
}
private void CreateCell(BoardCellData cell, ICellViewFactory cellViewFactory)
private CellView CreateCell(BoardCellData cell, ICellViewFactory cellViewFactory)
{
if (gridLayoutGroup == null)
{
return;
return null;
}
var view = cellViewFactory.CreateCell(cell, gridLayoutGroup.transform);
var view = GetOrCreateCell(cell, cellViewFactory);
if (view == null)
{
return;
return null;
}
view.SetInputEnabled(inputEnabled);
@@ -226,6 +240,22 @@ namespace Minesweeper.Presentation.Views
view.PressStarted += OnCellPressStarted;
view.PressEnded += OnCellPressEnded;
cellsByCoordinate[ToKey(cell.X, cell.Y)] = view;
return view;
}
private CellView GetOrCreateCell(BoardCellData cell, ICellViewFactory cellViewFactory)
{
CellView view;
if (pooledCells.Count > 0)
{
view = pooledCells.Pop();
view.transform.SetParent(gridLayoutGroup.transform, false);
view.Initialize(cell.X, cell.Y);
view.gameObject.SetActive(true);
return view;
}
return cellViewFactory.CreateCell(cell, gridLayoutGroup.transform);
}
private void Clear()
@@ -238,19 +268,24 @@ namespace Minesweeper.Presentation.Views
cell.FlagRequested -= OnCellFlagRequested;
cell.PressStarted -= OnCellPressStarted;
cell.PressEnded -= OnCellPressEnded;
PoolCell(cell);
}
}
cellsByCoordinate.Clear();
}
if (gridLayoutGroup == null)
{
return;
}
private void PoolCell(CellView cell)
{
cell.gameObject.SetActive(false);
pooledCells.Push(cell);
}
for (var i = gridLayoutGroup.transform.childCount - 1; i >= 0; i--)
private void SetGridLayoutEnabled(bool enabled)
{
if (gridLayoutGroup != null)
{
Destroy(gridLayoutGroup.transform.GetChild(i).gameObject);
gridLayoutGroup.enabled = enabled;
}
}
+30 -6
View File
@@ -39,6 +39,7 @@ namespace Minesweeper.Presentation.Views
{
this.x = x;
this.y = y;
gameObject.name = $"bt_{x}_{y}";
}
public void SetInputEnabled(bool enabled)
@@ -52,7 +53,6 @@ namespace Minesweeper.Presentation.Views
public void Render(BoardCellData cell, MinesweeperUiConfig config, float pixelsPerUnitMultiplier, float contentPadding, bool revealUnflaggedMines)
{
gameObject.name = $"bt_{cell.X}_{cell.Y}_{cell.DisplayValue}";
isOpened = cell.IsOpened;
var revealMine = revealUnflaggedMines && cell.IsMine && !cell.IsFlagged;
@@ -90,14 +90,14 @@ namespace Minesweeper.Presentation.Views
if (contentImage != null)
{
contentImage.gameObject.SetActive(showFlag || (showMine && !showMineAsText));
SetActiveIfChanged(contentImage.gameObject, showFlag || (showMine && !showMineAsText));
if (showFlag)
{
contentImage.sprite = config.FlagSprite;
SetSpriteIfChanged(contentImage, config.FlagSprite);
}
else if (showMine)
{
contentImage.sprite = config.MineSprite;
SetSpriteIfChanged(contentImage, config.MineSprite);
}
contentImage.color = Color.white;
@@ -105,12 +105,36 @@ namespace Minesweeper.Presentation.Views
if (label != null)
{
label.gameObject.SetActive(showNumber || showMineAsText);
label.text = showMineAsText ? "M" : showNumber ? cell.NeighborMines.ToString() : string.Empty;
SetActiveIfChanged(label.gameObject, showNumber || showMineAsText);
SetTextIfChanged(label, showMineAsText ? "M" : showNumber ? cell.NeighborMines.ToString() : string.Empty);
label.color = config.GetNumberTextColor(cell.NeighborMines);
}
}
private static void SetActiveIfChanged(GameObject target, bool active)
{
if (target.activeSelf != active)
{
target.SetActive(active);
}
}
private static void SetSpriteIfChanged(Image image, Sprite sprite)
{
if (image.sprite != sprite)
{
image.sprite = sprite;
}
}
private static void SetTextIfChanged(TMP_Text text, string value)
{
if (text.text != value)
{
text.text = value;
}
}
public void OnPointerClick(PointerEventData eventData)
{
if (!inputEnabled)