#if UNITY_EDITOR || DEVELOPMENT_BUILD using TMPro; using UnityEngine; using UnityEngine.UI; // ОТЛАДОЧНАЯ ПАНЕЛЬ для проверки баланса: кнопка "DEV" слева на экране. // // Есть только в редакторе и в Development Build -- в релизную сборку этот файл не попадает совсем. // "+1 час" не имеет своей логики: он двигает GameClock, и модули досчитывают время по своим обычным правилам, // ровно как при возвращении игрока в игру. Поэтому проверяешь настоящие правила, а не кнопку. // // Перемотка действует на то, что берёт время из GameClock (сейчас -- Население и походы). Обучение // войск пока идёт по реальным часам. public class DebugPanel : MonoBehaviour { static DebugPanel instance; GameObject panel; TextMeshProUGUI info; float nextRefresh; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static void Create() { if (instance != null) return; var go = new GameObject("DebugPanel", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); DontDestroyOnLoad(go); var canvas = go.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 1500; // поверх HUD и окон var scaler = go.GetComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920, 1080); scaler.matchWidthOrHeight = 0.5f; instance = go.AddComponent(); instance.Build(); } void Build() { // Панель -- внутри безопасной зоны, чтобы не уходила под вырез камеры var safe = new GameObject("SafeArea", typeof(RectTransform)).GetComponent(); safe.SetParent(transform, false); safe.anchorMin = Vector2.zero; safe.anchorMax = Vector2.one; safe.offsetMin = safe.offsetMax = Vector2.zero; safe.gameObject.AddComponent(); // Кнопка-переключатель слева посередине: там нет кнопок HUD var toggle = UIKit.Button(transform, "DEV", new Color(0.8f, 0.2f, 0.8f, 1f), Toggle, 110, 64, 28); var trt = (RectTransform)toggle.transform; trt.anchorMin = trt.anchorMax = new Vector2(0, 0.5f); trt.pivot = new Vector2(0, 0.5f); trt.anchoredPosition = new Vector2(12, 0); trt.sizeDelta = new Vector2(110, 64); var card = UIKit.Card(safe, new Color(0.08f, 0.08f, 0.11f, 0.95f), 20, 10); card.anchorMin = card.anchorMax = new Vector2(0, 0.5f); card.pivot = new Vector2(0, 0.5f); card.anchoredPosition = new Vector2(135, 0); card.sizeDelta = new Vector2(620, 0); card.gameObject.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; panel = card.gameObject; var title = UIKit.Text(card, "ОТЛАДКА БАЛАНСА", 30, UIKit.Theme.Gold, TextAlignmentOptions.Center); title.fontStyle = FontStyles.Bold; info = UIKit.Text(card, "", 24, UIKit.Theme.Text); var row1 = UIKit.Row(card, 70, 10); Btn(row1, "+1 час", UIKit.Theme.Blue, () => GameClock.Advance(3600)); Btn(row1, "+10 часов", UIKit.Theme.Blue, () => GameClock.Advance(36000)); var row2 = UIKit.Row(card, 70, 10); Btn(row2, "+100 всех ресурсов", UIKit.Theme.Green, AddAllResources); Btn(row2, "+1000 еды", UIKit.Theme.Green, () => AddResource(ResourceType.Food, 1000)); var row3 = UIKit.Row(card, 70, 10); Btn(row3, "Еда = 0", UIKit.Theme.Red, ZeroFood); Btn(row3, "Сбросить перемотку", UIKit.Theme.Gray, () => { GameClock.ResetOffset(); Refresh(); }); var row4 = UIKit.Row(card, 70, 10); Btn(row4, "Рабочие", UIKit.Theme.Blue, () => BuildingWindow.Open(null)); Btn(row4, "Закрыть", UIKit.Theme.Gray, () => panel.SetActive(false)); panel.SetActive(false); } static void Btn(Transform row, string label, Color color, System.Action onClick) { var b = UIKit.Button(row, label, color, onClick, -1, 70, 24); UIKit.Size(b, -1, 70, 1); } void Toggle() { panel.SetActive(!panel.activeSelf); if (panel.activeSelf) Refresh(); } void Update() { if (panel == null || !panel.activeSelf || Time.unscaledTime < nextRefresh) return; nextRefresh = Time.unscaledTime + 0.5f; Refresh(); } void Refresh() { if (info == null) return; var sb = new System.Text.StringBuilder(); if (GameServices.TryGet(out IPopulation p)) { sb.Append($"Население: {p.Total} / {p.HousingCap}\n"); sb.Append($" жители {p.Civilians}: на работе {p.Workers}, свободно {p.FreeWorkers} из {p.Workforce}; воины {p.Soldiers}\n"); sb.Append($"Еда в час: -{p.FoodPerHour:0.#} прирост в час: +{p.GrowthPerHour:0.#}\n"); if (p.IsStarving) sb.Append("ГОЛОД: еды не хватает, прирост остановлен\n"); } else sb.Append("Модуль Населения не зарегистрирован\n"); if (GameServices.TryGet(out IProduction prod)) sb.Append($"Добыча в час: еда +{prod.RatePerHour(ResourceType.Food):0.#}, дерево +{prod.RatePerHour(ResourceType.Wood):0.#}, " + $"камень +{prod.RatePerHour(ResourceType.Stone):0.#}, металл +{prod.RatePerHour(ResourceType.Iron):0.#}, золото +{prod.RatePerHour(ResourceType.Gold):0.#}\n"); if (GameServices.TryGet(out IEconomy e)) sb.Append($"Склад: еда {e.Get(ResourceType.Food):0}/{Cap(e, ResourceType.Food)} дерево {e.Get(ResourceType.Wood):0}/{Cap(e, ResourceType.Wood)} " + $"камень {e.Get(ResourceType.Stone):0}/{Cap(e, ResourceType.Stone)}\n"); else sb.Append("Экономика не найдена (нет ResourceManager на сцене)\n"); long off = GameClock.OffsetSeconds; sb.Append(off > 0 ? $"Перемотано: +{off / 3600f:0.#} ч" : "Время: реальное"); info.text = sb.ToString(); } static string Cap(IEconomy e, ResourceType t) { float c = e.GetCapacity(t); return float.IsInfinity(c) ? "∞" : c.ToString("0"); } void AddAllResources() { ResourceType[] types = { ResourceType.Wood, ResourceType.Food, ResourceType.Stone, ResourceType.Iron, ResourceType.Gold, ResourceType.Gems }; foreach (var t in types) AddResource(t, 100); } void AddResource(ResourceType type, float amount) { if (GameServices.TryGet(out IEconomy e)) e.Add(type, amount); Refresh(); } void ZeroFood() { if (GameServices.TryGet(out IEconomy e)) e.Add(ResourceType.Food, -e.Get(ResourceType.Food)); Refresh(); } void OnDestroy() { if (instance == this) instance = null; } } #endif