Danro/Assets/Scripts/Managers/BalanceManager.cs
Danil Iarosh 2417f58f61
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat: Add text input system and balance management with settings improvements
- Добавлена система ввода текста через модальное окно с ячейками для аркадного автомата
- Реализован TextInputModal с поддержкой кастомных алфавитов и навигации
- Добавлен TextInputCell для ввода отдельных символов с циклической навигацией
- Создан TextInputSettingsBinder для интеграции ввода текста с системой настроек
- Реализована компактная сериализация настроек (только нужные поля в JSON)

- Внедрена система баланса игрока (BalanceManager)
- Баланс начисляется при оплате импульсом, списывается при начале/продолжении игры
- Добавлен InactivityManager для выхода в меню при отсутствии активности (5 минут)

- Улучшена система настроек
- Добавлены флаги shownInSettings и exposed для управления видимостью настроек
- Реализована автоматическая регистрация/отписка SettingsButtonBinder при активации/деактивации
- Исправлена загрузка настроек с проверкой типов через Sanitize()
- SettingsApplier упрощен - оставлена только поддержка AudioManager
- Добавлен ValueSelectorRow для выбора целочисленных значений с шагом

- Удалены примеры и документация из production кода (QUICK_SETUP.md, TextInputExample, AdvancedLoggingExample)
- Убран весь отладочный вывод Debug.Log из системы настроек и ввода текста
- Очищены неразрывные пробелы из файлов проекта
- Добавлены префабы UI (InputCell)
2026-02-11 16:27:33 +03:00

330 lines
12 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 RR.Common.Controls;
using RR.Common.Settings;
using UnityEngine;
using UnityEngine.Events;
namespace RR.Common.Managers
{
/// <summary>
/// Менеджер игрового баланса. Управляет внутриигровой валютой,
/// которая начисляется за импульсы оплаты и списывается за начало/продолжение игры.
/// </summary>
public class BalanceManager : MonoBehaviour
{
public static BalanceManager Instance { get; private set; }
[Header("Текущий баланс")]
[SerializeField] private int currentBalance;
[Header("Настройки сохранения")]
[SerializeField] private bool saveBalanceBetweenSessions;
// События для уведомления UI и других систем
public UnityEvent<int> OnBalanceChanged;
public UnityEvent<int, int> OnBalanceAdded; // (добавлено, новый баланс)
public UnityEvent<int, int> OnBalanceSpent; // (потрачено, новый баланс)
/// <summary>
/// Текущий баланс игрока
/// </summary>
public int CurrentBalance => currentBalance;
private const string BALANCE_SAVE_KEY = "PlayerBalance";
// Кешированные значения из настроек
private int _impulseCost = 10;
private int _startGameCost = 10;
private int _continueGameCost = 5;
private bool _freeGame;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
Initialize();
}
private void Initialize()
{
// Инициализация событий
OnBalanceChanged ??= new UnityEvent<int>();
OnBalanceChanged ??= new UnityEvent<int>();
OnBalanceAdded ??= new UnityEvent<int, int>();
OnBalanceSpent ??= new UnityEvent<int, int>();
// Загрузка баланса из сохранений
if (saveBalanceBetweenSessions)
{
currentBalance = PlayerPrefs.GetInt(BALANCE_SAVE_KEY, 0);
}
else
{
currentBalance = 0;
PlayerPrefs.SetInt(BALANCE_SAVE_KEY, 0);
}
// Подписка на события настроек для кеширования значений
var settingsManager = SettingsManager.Instance;
if (settingsManager != null)
{
settingsManager.OnSettingsLoaded += CacheSettingsValues;
settingsManager.OnSettingsSaved += CacheSettingsValues;
CacheSettingsValues(); // Загрузка при инициализации
}
// Подписка на события оплаты
if (PaymentManager.Instance != null)
{
PaymentManager.Instance.OnCreditAdded.AddListener(OnImpulsePaid);
}
Debug.Log($"[BalanceManager] Инициализирован. Баланс: {currentBalance}");
}
private void OnDestroy()
{
if (PaymentManager.Instance != null)
{
PaymentManager.Instance.OnCreditAdded.RemoveListener(OnImpulsePaid);
}
var settingsManager = SettingsManager.Instance;
if (settingsManager != null)
{
settingsManager.OnSettingsLoaded -= CacheSettingsValues;
settingsManager.OnSettingsSaved -= CacheSettingsValues;
}
}
/// <summary>
/// Кеширует значения из настроек
/// </summary>
private void CacheSettingsValues()
{
var settings = SettingsManager.Instance;
if (settings == null)
{
Debug.LogError("[BalanceManager] SettingsManager is null, cannot cache settings!");
return;
}
_impulseCost = settings.GetInt("payment.paymentImpulseCost");
_startGameCost = settings.GetInt("payment.startGameCost");
_continueGameCost = settings.GetInt("payment.continueGameCost");
_freeGame = settings.GetBool("payment.freeGame");
// Валидация значений
if (_impulseCost <= 0)
{
Debug.LogError($"[BalanceManager] Invalid payment.paymentImpulseCost: {_impulseCost}");
_impulseCost = 10;
}
if (_startGameCost <= 0)
{
Debug.LogError($"[BalanceManager] Invalid payment.startGameCost: {_startGameCost}");
_startGameCost = 10;
}
if (_continueGameCost <= 0)
{
Debug.LogError($"[BalanceManager] Invalid payment.continueGameCost: {_continueGameCost}");
_continueGameCost = 5;
}
}
/// <summary>
/// Обработчик оплаты импульса - начисляет баланс
/// </summary>
private void OnImpulsePaid(int creditsCount)
{
// creditsCount - это количество добавленных кредитов (всегда 1 для одного импульса)
// Начисляем стоимость импульса
AddBalance(_impulseCost);
}
/// <summary>
/// Добавляет баланс игроку
/// </summary>
/// <param name="amount">Количество добавляемого баланса</param>
public void AddBalance(int amount)
{
if (amount <= 0)
{
Debug.LogWarning($"[BalanceManager] Trying to add invalid amount: {amount}");
return;
}
currentBalance += amount;
// Сохранение если включено
if (saveBalanceBetweenSessions)
{
PlayerPrefs.SetInt(BALANCE_SAVE_KEY, currentBalance);
PlayerPrefs.Save();
}
// Синхронизация кредитов с балансом
SyncCreditsWithBalance();
// Уведомление подписчиков
OnBalanceAdded?.Invoke(amount, currentBalance);
OnBalanceChanged?.Invoke(currentBalance);
}
/// <summary>
/// Проверяет, достаточно ли баланса для начала игры
/// </summary>
public bool CanStartGame()
{
// Проверка бесплатной игры
if (_freeGame)
return true;
return currentBalance >= _startGameCost;
}
/// <summary>
/// Проверяет, достаточно ли баланса для продолжения игры
/// </summary>
public bool CanContinueGame()
{
// Проверка бесплатной игры
if (_freeGame)
return true;
return currentBalance >= _continueGameCost;
}
/// <summary>
/// Списывает баланс за начало игры
/// </summary>
/// <returns>true если списание успешно</returns>
public bool SpendForStart()
{
// Проверка бесплатной игры
if (_freeGame)
return true;
return SpendBalance(_startGameCost, "начало игры");
}
/// <summary>
/// Списывает баланс за продолжение игры
/// </summary>
/// <returns>true если списание успешно</returns>
public bool SpendForContinue()
{
// Проверка бесплатной игры
if (_freeGame)
return true;
return SpendBalance(_continueGameCost, "продолжение игры");
}
/// <summary>
/// Списывает указанное количество баланса
/// </summary>
/// <param name="amount">Количество списываемого баланса</param>
/// <param name="reason">Причина списания (для логирования)</param>
/// <returns>true если списание успешно</returns>
public bool SpendBalance(int amount, string reason = "")
{
if (amount <= 0)
{
Debug.LogWarning($"[BalanceManager] Trying to spend invalid amount: {amount}");
return false;
}
if (currentBalance < amount)
{
return false;
}
currentBalance -= amount;
// Сохранение если включено
if (saveBalanceBetweenSessions)
{
PlayerPrefs.SetInt(BALANCE_SAVE_KEY, currentBalance);
PlayerPrefs.Save();
}
// Синхронизация кредитов с балансом
SyncCreditsWithBalance();
// Уведомление подписчиков
OnBalanceSpent?.Invoke(amount, currentBalance);
OnBalanceChanged?.Invoke(currentBalance);
return true;
}
/// <summary>
/// Сбрасывает баланс до нуля
/// </summary>
public void ResetBalance()
{
currentBalance = 0;
if (saveBalanceBetweenSessions)
{
PlayerPrefs.SetInt(BALANCE_SAVE_KEY, 0);
PlayerPrefs.Save();
}
// Синхронизация кредитов с балансом
SyncCreditsWithBalance();
OnBalanceChanged?.Invoke(currentBalance);
}
/// <summary>
/// Синхронизирует кредиты в ComWrapper с текущим балансом
/// по формуле: кредиты = баланс / стоимость_импульса (округление вниз)
/// </summary>
private void SyncCreditsWithBalance()
{
if (_impulseCost <= 0)
{
Debug.LogError("[BalanceManager] Cannot sync credits - invalid impulse cost");
return;
}
int targetCredits = currentBalance / _impulseCost;
int currentCredits = ComWrapper.GetCredits();
if (targetCredits == currentCredits)
return; // Кредиты уже синхронизированы
// Устанавливаем кредиты напрямую через SetCredits
ComWrapper.SetCredits(targetCredits);
}
/// <summary>
/// Устанавливает баланс напрямую (для тестирования/отладки)
/// </summary>
public void SetBalance(int amount)
{
currentBalance = Mathf.Max(0, amount);
if (saveBalanceBetweenSessions)
{
PlayerPrefs.SetInt(BALANCE_SAVE_KEY, currentBalance);
PlayerPrefs.Save();
}
// Синхронизация кредитов с балансом
SyncCreditsWithBalance();
OnBalanceChanged?.Invoke(currentBalance);
}
}
}