Слияние: наши панели HUD, стройка, казармы, камера + карта мира, чат и рейтинг от Саши
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
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<PlacedBuildingId>() : null;
|
||||
UIKit.Toast("Участок занят" + (id != null ? ": " + id.buildingName : ""));
|
||||
return;
|
||||
}
|
||||
OpenChooser(slot);
|
||||
}
|
||||
|
||||
// ---------------- участки ----------------
|
||||
|
||||
List<BuildSlot> FreeSlots()
|
||||
{
|
||||
var list = new List<BuildSlot>();
|
||||
foreach (var s in FindObjectsByType<BuildSlot>(FindObjectsSortMode.None)) if (!s.isOccupied) list.Add(s);
|
||||
return list;
|
||||
}
|
||||
|
||||
void ApplySlotVisuals()
|
||||
{
|
||||
foreach (var s in FindObjectsByType<BuildSlot>(FindObjectsSortMode.None))
|
||||
{
|
||||
var sr = s.GetComponent<SpriteRenderer>();
|
||||
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<ContentSizeFitter>();
|
||||
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<BuildingData>(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<string>();
|
||||
foreach (var c in d.cost)
|
||||
{
|
||||
bool ok = ResourceManager.Instance != null && ResourceManager.Instance.HasEnough(c.type, c.amount);
|
||||
parts.Add("<color=" + (ok ? "#9be59b" : "#ff7a7a") + ">" + GameData.Short(c.amount) + "</color> " + 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<TextMeshProUGUI>();
|
||||
if (t != null) t.text = IsActive ? "ГОТОВО" : "СТРОЙКА";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user