using System; using System.Collections.Generic; using UnityEngine; // МОДУЛЬ ЭКОНОМИКИ. Центральное хранилище всех ресурсов игрока. // Другие модули работают с ним только через контракт IEconomy (GameServices.Get()), // а изменения узнают из события ResourceChangedEvent. Документ: Docs/Modules/Economy.md // // Старый доступ ResourceManager.Instance оставлен для интерфейса (панели, HUD) -- модулям им пользоваться нельзя. public class ResourceManager : MonoBehaviour, IEconomy, ISaveParticipant { public static ResourceManager Instance { get; private set; } // Событие для интерфейса (HUD). Модули слушают ResourceChangedEvent через EventBus. public event Action OnResourceChanged; [Header("Стартовые значения (для теста/баланса)")] public float startWood = 0f; public float startStone = 0f; public float startIron = 0f; public float startFood = 0f; public float startGold = 0f; public float startGems = 0f; public float startPower = 0f; public float startPopulation = 0f; private Dictionary resources = new Dictionary(); void Awake() { // Классический паттерн Singleton: если уже есть экземпляр -- уничтожаем дубликат if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // менеджер ресурсов должен жить между сценами (меню -> игра) SetStartValues(notify: false); GameServices.Register(this); SaveRegistry.Register(this); } void OnDestroy() { if (Instance != this) return; GameServices.Unregister(this); SaveRegistry.Unregister(this); } // Добавить ресурс (можно и отрицательное число -- для трат) public void AddResource(ResourceType type, float amount) { if (!resources.ContainsKey(type)) resources[type] = 0f; resources[type] += amount; if (resources[type] < 0f) resources[type] = 0f; // ресурс не уходит в минус NotifyChanged(type); } // Проверить, хватает ли ресурса (для трат/построек в будущем) public bool HasEnough(ResourceType type, float amount) { return GetResource(type) >= amount; } // Попробовать потратить -- вернёт false, если не хватило (и ничего не спишет) public bool TrySpend(ResourceType type, float amount) { if (!HasEnough(type, amount)) return false; AddResource(type, -amount); return true; } public float GetResource(ResourceType type) { return resources.TryGetValue(type, out float value) ? value : 0f; } // Устанавливает значение напрямую, БЕЗ проверок и без учёта прошлого значения. // Используется только при загрузке сохранения -- обычная игровая логика должна // использовать AddResource/TrySpend, а не этот метод. public void SetResource(ResourceType type, float value) { resources[type] = value; NotifyChanged(type); } // Все ресурсы разом -- удобно для сохранения public Dictionary GetAllResources() { return new Dictionary(resources); } void SetStartValues(bool notify) { var start = new Dictionary { { ResourceType.Wood, startWood }, { ResourceType.Stone, startStone }, { ResourceType.Iron, startIron }, { ResourceType.Food, startFood }, { ResourceType.Gold, startGold }, { ResourceType.Gems, startGems }, { ResourceType.Power, startPower }, { ResourceType.Population, startPopulation }, }; resources.Clear(); foreach (var kvp in start) { resources[kvp.Key] = kvp.Value; if (notify) NotifyChanged(kvp.Key); } } void NotifyChanged(ResourceType type) { float value = resources[type]; OnResourceChanged?.Invoke(type, value); EventBus.Publish(new ResourceChangedEvent { Type = type, Value = value }); } // ---------------- Контракт IEconomy ---------------- float IEconomy.Get(ResourceType type) => GetResource(type); bool IEconomy.Has(ResourceType type, float amount) => HasEnough(type, amount); void IEconomy.Add(ResourceType type, float amount) => AddResource(type, amount); public bool CanAfford(IReadOnlyList cost) { foreach (var c in cost) { if (!HasEnough(c.Type, c.Amount)) return false; } return true; } public bool TrySpend(IReadOnlyList cost) { if (!CanAfford(cost)) return false; foreach (var c in cost) AddResource(c.Type, -c.Amount); return true; } // ---------------- Сохранение (секция "economy") ---------------- [Serializable] class ResourceSaveEntry { public ResourceType type; public float amount; } // Имена полей совпадают со старым save.json -- старые сохранения читаются без переделки [Serializable] class EconomySaveData { public List resources = new List(); } public string SaveKey => "economy"; public string Capture() { var data = new EconomySaveData(); foreach (var kvp in resources) data.resources.Add(new ResourceSaveEntry { type = kvp.Key, amount = kvp.Value }); return JsonUtility.ToJson(data); } public void Restore(string json) { var data = JsonUtility.FromJson(json); if (data?.resources == null) return; foreach (var entry in data.resources) SetResource(entry.type, entry.amount); } // Новая игра -- стартовые значения из Inspector public void ResetState() { SetStartValues(notify: true); } }