Files
MVP1/Assets/Scripts/Modules/Economy/ResourceManager.cs
T

221 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using UnityEngine;
// МОДУЛЬ ЭКОНОМИКИ. Центральное хранилище всех ресурсов игрока.
// Другие модули работают с ним только через контракт IEconomy (GameServices.Get<IEconomy>()),
// а изменения узнают из события 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<ResourceType, float> 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<ResourceType, float> resources = new Dictionary<ResourceType, float>();
// Лимиты склада (этап 1). Нет записи -- лимита нет. Задаёт слой приложения (App/CitySync) по Складам и Амбарам,
// запоминаются в сохранении: на карте мира город не открыт, а лимиты действуют.
private Dictionary<ResourceType, float> capacities = new Dictionary<ResourceType, float>();
// Лимит изменился (для интерфейса: "запас / лимит")
public event Action<ResourceType, float> OnCapacityChanged;
void Awake()
{
// Классический паттерн Singleton: если уже есть экземпляр -- уничтожаем дубликат
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // менеджер ресурсов должен жить между сценами (меню -> игра)
SetStartValues(notify: false);
GameServices.Register<IEconomy>(this);
SaveRegistry.Register(this);
}
void OnDestroy()
{
if (Instance != this) return;
GameServices.Unregister<IEconomy>(this);
SaveRegistry.Unregister(this);
}
// Добавить ресурс (можно и отрицательное число -- для трат).
// Прибавка не поднимает запас выше лимита склада -- лишнее пропадает. Если запас уже выше лимита
// (старое сохранение, лимит уменьшился), он не урезается, просто больше не растёт.
public void AddResource(ResourceType type, float amount)
{
if (!resources.ContainsKey(type))
resources[type] = 0f;
float current = resources[type];
float next = current + amount;
if (amount > 0f && capacities.TryGetValue(type, out float cap))
next = Mathf.Min(next, Mathf.Max(current, cap));
if (next < 0f) next = 0f; // ресурс не уходит в минус
if (next == current) return; // ничего не изменилось (например, склад полон) -- не будим интерфейс
resources[type] = next;
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;
}
// Лимит склада: float.PositiveInfinity -- без лимита
public float GetCapacity(ResourceType type)
{
return capacities.TryGetValue(type, out float cap) ? cap : float.PositiveInfinity;
}
public void SetCapacity(ResourceType type, float capacity)
{
if (capacities.TryGetValue(type, out float old) && Mathf.Approximately(old, capacity)) return;
capacities[type] = Mathf.Max(0f, capacity);
OnCapacityChanged?.Invoke(type, capacities[type]);
}
// Устанавливает значение напрямую, БЕЗ проверок и без учёта прошлого значения.
// Используется только при загрузке сохранения -- обычная игровая логика должна
// использовать AddResource/TrySpend, а не этот метод.
public void SetResource(ResourceType type, float value)
{
resources[type] = value;
NotifyChanged(type);
}
// Все ресурсы разом -- удобно для сохранения
public Dictionary<ResourceType, float> GetAllResources()
{
return new Dictionary<ResourceType, float>(resources);
}
void SetStartValues(bool notify)
{
var start = new Dictionary<ResourceType, float>
{
{ 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);
float IEconomy.GetCapacity(ResourceType type) => GetCapacity(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<ResourceAmount> cost)
{
foreach (var c in cost)
{
if (!HasEnough(c.Type, c.Amount)) return false;
}
return true;
}
public bool TrySpend(IReadOnlyList<ResourceAmount> 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<ResourceSaveEntry> resources = new List<ResourceSaveEntry>();
public List<ResourceSaveEntry> capacities = new List<ResourceSaveEntry>(); // лимиты склада (этап 1)
}
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 });
foreach (var kvp in capacities)
data.capacities.Add(new ResourceSaveEntry { type = kvp.Key, amount = kvp.Value });
return JsonUtility.ToJson(data);
}
public void Restore(string json)
{
var data = JsonUtility.FromJson<EconomySaveData>(json);
if (data?.resources == null) return;
if (data.capacities != null)
foreach (var entry in data.capacities) SetCapacity(entry.type, entry.amount);
foreach (var entry in data.resources) SetResource(entry.type, entry.amount);
}
// Новая игра -- стартовые значения из Inspector
public void ResetState()
{
SetStartValues(notify: true);
}
}