188 lines
6.7 KiB
C#
188 lines
6.7 KiB
C#
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>();
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
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<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);
|
|||
|
|
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 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<EconomySaveData>(json);
|
|||
|
|
if (data?.resources == null) return;
|
|||
|
|
foreach (var entry in data.resources) SetResource(entry.type, entry.amount);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Новая игра -- стартовые значения из Inspector
|
|||
|
|
public void ResetState()
|
|||
|
|
{
|
|||
|
|
SetStartValues(notify: true);
|
|||
|
|
}
|
|||
|
|
}
|