Files
MVP1/Assets/Scripts/Modules/Economy/ResourceManager.cs
T
AlexanderandClaude Opus 5.5 964cb23d9d Модульная система: игровое ядро + 6 модулей, документы Docs/Modules
- Ядро (Bogatyri.Core): контракты модулей, EventBus, GameServices, SaveRegistry
- Модули: Экономика, Поселение, Армия, Глобальный мир, Боевая система, Сохранения
  (каждый — отдельный asmdef, видит только ядро)
- App/GameModules: регистрация модулей и связь с GameProgress/Казармами
- Сохранения v2 по секциям модулей, перенос старого save.json и походов из progress.json
- Сброс сохранений: ISaveSystem.ResetAll, ResetState у модулей, меню Tools
- EditMode-тесты модулей, документы по каждому модулю с журналом изменений

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:39:28 +02:00

188 lines
6.7 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>();
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);
}
}