using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; // НОВАЯ СХЕМА СТРОЙКИ (как в Rise of Kingdoms): // 1. Кнопка "СТРОЙКА" включает режим строительства -- появляются свободные участки и подсказка сверху. // 2. Тап по свободному участку -> окно со зданиями (цена, что доступно по уровню Ратуши). // 3. "Построить" -> здание встаёт на участок. Режим остаётся включённым, можно строить дальше. // 4. Кнопка "ВЫЙТИ" (та же кнопка или в подсказке) -- выключает режим, участки снова прячутся. // Вне режима пустые участки не видны и не мешают, при запуске игры ничего не открывается. // // Вешается билдером (Tools -> Build HUD Panels) на HUD_Canvas, кнопка Nav_Build вызывает ToggleMode(). public class BuildModeController : MonoBehaviour { public static BuildModeController Instance { get; private set; } [Tooltip("Кнопка HUD, которая включает/выключает режим (надпись меняется сама)")] public Button modeButton; [Tooltip("Старое меню стройки -- прячем, оно больше не используется")] public GameObject legacyBuildMenu; public bool IsActive { get; private set; } GameObject banner; GameObject chooser; Vector2 downPos; bool downOverUI; float nextVisualRefresh; static readonly Color SlotColor = new Color(0.3f, 1f, 0.4f, 0.75f); void Awake() { Instance = this; } void OnDestroy() { if (Instance == this) Instance = null; } void Start() { if (legacyBuildMenu != null) legacyBuildMenu.SetActive(false); SetMode(false); } public void ToggleMode() { SetMode(!IsActive); } public void SetMode(bool active) { IsActive = active; CloseChooser(); ApplySlotVisuals(); UpdateButtonLabel(); if (banner != null) { Destroy(banner); banner = null; } if (active) { if (UIPanelManager.Instance != null) UIPanelManager.Instance.CloseAll(); banner = CreateBanner(); if (FreeSlots().Count == 0) UIKit.Toast("Свободных участков нет. Улучшайте Ратушу и сносите старое."); } } void Update() { if (Time.unscaledTime >= nextVisualRefresh) { nextVisualRefresh = Time.unscaledTime + 0.3f; ApplySlotVisuals(); // участки могли освободиться/заняться (снос, загрузка сохранения) } if (!IsActive) return; // открыли любую панель HUD -- выходим из режима стройки if (UIPanelManager.Instance != null && UIPanelManager.Instance.CurrentId != "") { SetMode(false); return; } if (chooser != null) return; // Тап = нажали и отпустили почти на том же месте (чтобы перетаскивание камеры не считалось тапом) if (Input.GetMouseButtonDown(0)) { downPos = Input.mousePosition; downOverUI = UIBlocker.IsPointerOverUI(); } if (Input.GetMouseButtonUp(0)) { Vector2 up = Input.mousePosition; if (downOverUI || UIBlocker.IsPointerOverUI() || (up - downPos).magnitude > 25f) return; HandleTap(up); } } void HandleTap(Vector2 screenPos) { if (BuildManager.Instance == null) return; var slot = BuildManager.Instance.SlotAtScreen(screenPos); if (slot == null) return; if (slot.isOccupied) { var id = slot.occupyingBuilding != null ? slot.occupyingBuilding.GetComponent() : null; UIKit.Toast("Участок занят" + (id != null ? ": " + id.buildingName : "")); return; } OpenChooser(slot); } // ---------------- участки ---------------- List FreeSlots() { var list = new List(); foreach (var s in FindObjectsByType(FindObjectsSortMode.None)) if (!s.isOccupied) list.Add(s); return list; } void ApplySlotVisuals() { foreach (var s in FindObjectsByType(FindObjectsSortMode.None)) { var sr = s.GetComponent(); if (sr == null) continue; bool show = IsActive && !s.isOccupied; if (sr.enabled != show) sr.enabled = show; if (show) sr.color = SlotColor; } } // ---------------- окно выбора здания ---------------- void OpenChooser(BuildSlot slot) { CloseChooser(); var bm = BuildManager.Instance; GameObject root; var card = UIKit.Modal("Что построить?", out root); chooser = root; // окно фиксированного размера с прокруткой, чтобы влезало на любом экране var fit = card.GetComponent(); if (fit != null) DestroyImmediate(fit); card.anchorMin = new Vector2(0.08f, 0.08f); card.anchorMax = new Vector2(0.92f, 0.92f); card.offsetMin = Vector2.zero; card.offsetMax = Vector2.zero; int th = TownHall.Instance != null ? TownHall.Instance.currentLevel : 1; UIKit.Text(card, "Участок: " + slot.name + " • Ратуша " + th + " ур.", 24, UIKit.Theme.TextDim, TextAlignmentOptions.Center); var holder = UIKit.Make("ListHolder", card); UIKit.Size(holder, -1, -1).flexibleHeight = 1; ScrollRect scroll; var list = UIKit.ScrollList(holder, out scroll, 10); // доступные сверху, закрытые по уровню Ратуши -- ниже var all = new List(bm.allBuildings); all.RemoveAll(b => b == null); all.Sort((a, b) => a.requiredTownHallLevel.CompareTo(b.requiredTownHallLevel)); foreach (var data in all) { var d = data; bool locked = d.requiredTownHallLevel > th; bool afford = !locked && d.CanAfford(); var row = UIKit.Row(list, 120, 14, locked ? UIKit.Theme.CardDark : UIKit.Theme.Card, 10); var icon = UIKit.Box(row, d.icon != null ? Color.white : UIKit.Theme.Gray); if (d.icon != null) { icon.sprite = d.icon; icon.preserveAspect = true; } UIKit.Size(icon, 100, 100); var info = UIKit.Card(row, new Color(0, 0, 0, 0), 0, 4); UIKit.Size(info, -1, -1, 1); UIKit.Text(info, d.buildingName, 30, locked ? UIKit.Theme.TextDim : UIKit.Theme.Text).fontStyle = FontStyles.Bold; UIKit.Text(info, locked ? "Откроется на Ратуше " + d.requiredTownHallLevel + " уровня" : CostText(d), 24, locked ? UIKit.Theme.Gold : UIKit.Theme.TextDim); string label = locked ? "Закрыто" : (afford ? "Построить" : "Мало ресурсов"); var b = UIKit.Button(row, label, afford ? UIKit.Theme.Green : UIKit.Theme.Gray, () => { if (BuildManager.Instance.BuildOnSlot(d, slot)) { UIKit.Toast("Построено: " + d.buildingName); CloseChooser(); ApplySlotVisuals(); if (FreeSlots().Count == 0) SetMode(false); } else UIKit.Toast(GameProgress.Instance.MissingText(ToRewards(d.cost))); }, 230, 90, 26); b.interactable = !locked; } UIKit.Button(card, "Отмена", UIKit.Theme.Gray, CloseChooser, -1, 90, 30); } void CloseChooser() { if (chooser != null) Destroy(chooser); chooser = null; } static RewardItem[] ToRewards(ResourceCost[] cost) { if (cost == null) return new RewardItem[0]; var r = new RewardItem[cost.Length]; for (int i = 0; i < cost.Length; i++) r[i] = new RewardItem(cost[i].type, Mathf.CeilToInt(cost[i].amount)); return r; } // Цена по-русски, красным -- чего не хватает static string CostText(BuildingData d) { if (d.cost == null || d.cost.Length == 0) return "Бесплатно"; var parts = new List(); foreach (var c in d.cost) { bool ok = ResourceManager.Instance != null && ResourceManager.Instance.HasEnough(c.type, c.amount); parts.Add("" + GameData.Short(c.amount) + " " + GameData.ResName(c.type)); } return string.Join(", ", parts.ToArray()); } // ---------------- подсказка сверху и кнопка ---------------- GameObject CreateBanner() { // полоска сверху экрана (не модальная: остальной экран остаётся кликабельным) var bar = UIKit.Row(UIKit.Overlay, -1, 16, new Color(0.1f, 0.25f, 0.12f, 0.92f), 14); bar.name = "BuildModeBanner"; bar.anchorMin = new Vector2(0.15f, 1); bar.anchorMax = new Vector2(0.85f, 1); bar.pivot = new Vector2(0.5f, 1); bar.sizeDelta = new Vector2(0, 110); bar.anchoredPosition = new Vector2(0, -150); var t = UIKit.Text(bar, "РЕЖИМ СТРОЙКИ: нажмите на зелёный участок", 30, Color.white, TextAlignmentOptions.Left); UIKit.Size(t, -1, -1, 1); UIKit.Button(bar, "Выйти", UIKit.Theme.Red, () => SetMode(false), 180, 80, 28); return bar.gameObject; } void UpdateButtonLabel() { if (modeButton == null) return; var t = modeButton.GetComponentInChildren(); if (t != null) t.text = IsActive ? "ГОТОВО" : "СТРОЙКА"; } }