From 285c11597a314b2d106419a22cdc09cf77726127 Mon Sep 17 00:00:00 2001 From: Konstantin Dyachenko Date: Sun, 7 Jun 2026 00:57:20 +0700 Subject: [PATCH] [Fix] Board Optimization, Fix Screen Loading --- Assets/Config/MinesweeperGameConfig.asset | 8 +-- Assets/Scenes/SampleScene.unity | 9 ++- .../Config/MinesweeperScreenCatalog.cs | 16 ----- .../Config/MinesweeperScreenCatalog.cs.meta | 2 - Assets/Scripts/ECS/Systems.meta | 8 --- .../MinesweeperLifetimeScope.cs | 17 +++-- .../MinesweeperScreenBootstrapper.cs | 50 ++++++-------- .../Scripts/Presentation/Views/BoardView.cs | 65 ++++++++++++++----- Assets/Scripts/Presentation/Views/CellView.cs | 36 ++++++++-- 9 files changed, 117 insertions(+), 94 deletions(-) delete mode 100644 Assets/Scripts/Config/MinesweeperScreenCatalog.cs delete mode 100644 Assets/Scripts/Config/MinesweeperScreenCatalog.cs.meta delete mode 100644 Assets/Scripts/ECS/Systems.meta diff --git a/Assets/Config/MinesweeperGameConfig.asset b/Assets/Config/MinesweeperGameConfig.asset index 4293807..7da8db1 100644 --- a/Assets/Config/MinesweeperGameConfig.asset +++ b/Assets/Config/MinesweeperGameConfig.asset @@ -12,7 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b4e8d5c36f36bb443b640a85df3e7077, type: 3} m_Name: MinesweeperGameConfig m_EditorClassIdentifier: Assembly-CSharp::Minesweeper.Config.MinesweeperGameConfig - k__BackingField: 2 - k__BackingField: 50 - k__BackingField: 2 - k__BackingField: 50 + k__BackingField: 3 + k__BackingField: 30 + k__BackingField: 3 + k__BackingField: 30 diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index a9da44f..fc76a75 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -629,13 +629,12 @@ MonoBehaviour: autoInjectGameObjects: [] gameConfig: {fileID: 11400000, guid: 4c24a7c2a548eff4fb21fa4a4bf3e741, type: 2} uiConfig: {fileID: 11400000, guid: c8b7785713c7c8b49b853f7e5028a4fa, type: 2} - screenCatalog: - k__BackingField: {fileID: 7682962739562644362, guid: 66407cd7142d6a945b37ca8dc5e7c6b7, type: 3} - k__BackingField: {fileID: 463985621338212375, guid: 91c5885a4fbe47540abf4bfd814a32d0, type: 3} - k__BackingField: {fileID: 2814582388565546678, guid: ec358f6fec19e3b469f516bd1ade70cd, type: 3} - k__BackingField: {fileID: 6869455415096409219, guid: 73d5a09dc8885e64a8c20a68ea82c5dc, type: 3} contentRoot: {fileID: 1373940536} topPanelView: {fileID: 1101876296} + mainMenuViewPrefab: {fileID: 0} + boardViewPrefab: {fileID: 0} + pauseViewPrefab: {fileID: 0} + resultViewPrefab: {fileID: 0} --- !u!4 &1287266282 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Config/MinesweeperScreenCatalog.cs b/Assets/Scripts/Config/MinesweeperScreenCatalog.cs deleted file mode 100644 index 17b95e3..0000000 --- a/Assets/Scripts/Config/MinesweeperScreenCatalog.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using UnityEngine; - -namespace Minesweeper.Config -{ - [Serializable] - public sealed class MinesweeperScreenCatalog - { - [field: SerializeField] public GameObject MainMenuPanelPrefab { get; private set; } - [field: SerializeField] public GameObject BoardGridPrefab { get; private set; } - [field: SerializeField] public GameObject PausePanelPrefab { get; private set; } - [field: SerializeField] public GameObject ResultPanelPrefab { get; private set; } - - public bool IsValid => MainMenuPanelPrefab != null && BoardGridPrefab != null && PausePanelPrefab != null && ResultPanelPrefab != null; - } -} diff --git a/Assets/Scripts/Config/MinesweeperScreenCatalog.cs.meta b/Assets/Scripts/Config/MinesweeperScreenCatalog.cs.meta deleted file mode 100644 index bedaa12..0000000 --- a/Assets/Scripts/Config/MinesweeperScreenCatalog.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 6d0009ea7e70ee548a09cd95d482ec83 \ No newline at end of file diff --git a/Assets/Scripts/ECS/Systems.meta b/Assets/Scripts/ECS/Systems.meta deleted file mode 100644 index 38de214..0000000 --- a/Assets/Scripts/ECS/Systems.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 210d08f5d5948674fa0df51ed6a785b0 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Infrastructure/MinesweeperLifetimeScope.cs b/Assets/Scripts/Infrastructure/MinesweeperLifetimeScope.cs index 7371f0e..ee065ac 100644 --- a/Assets/Scripts/Infrastructure/MinesweeperLifetimeScope.cs +++ b/Assets/Scripts/Infrastructure/MinesweeperLifetimeScope.cs @@ -17,9 +17,12 @@ namespace Minesweeper.Infrastructure { [SerializeField] private MinesweeperGameConfig gameConfig; [SerializeField] private MinesweeperUiConfig uiConfig; - [SerializeField] private MinesweeperScreenCatalog screenCatalog = new MinesweeperScreenCatalog(); [SerializeField] private Transform contentRoot; [SerializeField] private TopPanelView topPanelView; + [SerializeField] private MainMenuView mainMenuViewPrefab; + [SerializeField] private BoardView boardViewPrefab; + [SerializeField] private PauseView pauseViewPrefab; + [SerializeField] private ResultView resultViewPrefab; protected override void Configure(IContainerBuilder builder) { @@ -124,12 +127,12 @@ namespace Minesweeper.Infrastructure private MinesweeperScreenRefs SpawnScreens() { - if (contentRoot == null || screenCatalog == null || !screenCatalog.IsValid) - { - return default; - } - - return new MinesweeperScreenBootstrapper().Spawn(contentRoot, screenCatalog); + return new MinesweeperScreenBootstrapper().Spawn( + contentRoot, + mainMenuViewPrefab, + boardViewPrefab, + pauseViewPrefab, + resultViewPrefab); } } } diff --git a/Assets/Scripts/Presentation/Factories/MinesweeperScreenBootstrapper.cs b/Assets/Scripts/Presentation/Factories/MinesweeperScreenBootstrapper.cs index 0f3c175..5d610f2 100644 --- a/Assets/Scripts/Presentation/Factories/MinesweeperScreenBootstrapper.cs +++ b/Assets/Scripts/Presentation/Factories/MinesweeperScreenBootstrapper.cs @@ -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(mainMenu.transform, MainMenuPanelName); if (mainMenuView != null) { - mainMenuView.BindRoot(mainMenu); + mainMenuView.BindRoot(mainMenuView.gameObject); } - var boardView = RequireComponent(board.transform, BoardGridName); - var pauseView = RequireComponent(pause.transform, PausePanelName); - var resultView = RequireComponent(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 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()); return instance; @@ -75,17 +74,6 @@ namespace Minesweeper.Presentation.Factories } } - private static T RequireComponent(Transform root, string ownerName) where T : Component - { - var component = root.GetComponent(); - 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) diff --git a/Assets/Scripts/Presentation/Views/BoardView.cs b/Assets/Scripts/Presentation/Views/BoardView.cs index 29bb6d2..0c17a91 100644 --- a/Assets/Scripts/Presentation/Views/BoardView.cs +++ b/Assets/Scripts/Presentation/Views/BoardView.cs @@ -23,6 +23,7 @@ namespace Minesweeper.Presentation.Views [SerializeField] private MinesweeperUiConfig uiConfig; private readonly Dictionary cellsByCoordinate = new Dictionary(); + private readonly Stack pooledCells = new Stack(); private IReadOnlyList 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 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; } } diff --git a/Assets/Scripts/Presentation/Views/CellView.cs b/Assets/Scripts/Presentation/Views/CellView.cs index 4165ace..c0aacd8 100644 --- a/Assets/Scripts/Presentation/Views/CellView.cs +++ b/Assets/Scripts/Presentation/Views/CellView.cs @@ -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)