Danro/Assets/Scripts/Settings/Core/SettingsManager.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

403 lines
14 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;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace RR.Common.Settings
{
[Serializable]
public class SettingsSaveData
{
public int version = 1;
public List<Entry> entries = new();
[Serializable]
public class Entry
{
public string key;
public SettingValueCompact value; // Используем компактную версию
}
}
[DefaultExecutionOrder(-900)]
public class SettingsManager : MonoBehaviour
{
[SerializeField] private SettingsDatabase database;
public static SettingsManager Instance { get; private set; }
private readonly Dictionary<string, SettingValue> map = new();
private string path;
private Coroutine saveRoutine;
// События для совместимости со старым кодом
public event Action OnSettingsLoaded;
public event Action OnSettingsSaved;
public event Action<string, SettingValue> OnSettingChanged;
private void Awake()
{
if (Instance)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
path = Path.Combine(Application.persistentDataPath, "settings.json");
if (!database)
{
Debug.LogError("[SettingsManager] Database is not assigned! Settings will not work.");
return;
}
LoadOrDefaults();
ApplyAll();
}
/// <summary>
/// Инициализация из GameManager (альтернатива Awake)
/// </summary>
public void Initialize()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
// Если database не назначена - пытаемся загрузить из Resources
if (!database)
{
database = Resources.Load<SettingsDatabase>("SettingsDatabase");
if (!database)
{
// Пытаемся найти л<><D0BB>бую SettingsDatabase в проекте
var databases = Resources.FindObjectsOfTypeAll<SettingsDatabase>();
if (databases.Length > 0)
{
database = databases[0];
}
}
}
if (!database)
{
Debug.LogError("[SettingsManager] Database is not assigned and not found in Resources! Settings will not work.");
return;
}
path = Path.Combine(Application.persistentDataPath, "settings.json");
LoadOrDefaults();
ApplyAll();
}
public SettingEntry Define(string key)
{
if (!database)
{
Debug.LogWarning($"[SettingsManager] Database is null, cannot define key '{key}'");
return null;
}
return database.Find(key);
}
public SettingValue Get(string key)
{
if (map.TryGetValue(key, out SettingValue v)) return v;
SettingEntry def = Define(key);
return def?.DefaultValue ?? default;
}
// Удобные типизированные геттеры
public bool GetBool(string key) => Get(key).b;
public int GetInt(string key) => Get(key).i;
public float GetFloat(string key) => Get(key).f;
public string GetString(string key) => Get(key).s;
public void Set(string key, SettingValue value)
{
// Проверяем, что объект не уничтожен
if (this == null) return;
SettingEntry def = Define(key);
if (def == null)
{
Debug.LogWarning($"[SettingsManager] No definition found for key '{key}'. Check that SettingsDatabase has this entry.");
return;
}
SettingValue clean = def.Sanitize(value);
map[key] = clean;
// применяем СРАЗУ
SettingsApplier.Apply(def, clean);
// оповещаем подписчиков
OnSettingChanged?.Invoke(key, clean);
// сохраняем с небольшой задержкой (удобно для слайдеров)
ScheduleSave(0.25f);
}
// Удобные типизированные сеттеры
public void SetBool(string key, bool value) => Set(key, SettingValue.Bool(value));
public void SetInt(string key, int value) => Set(key, SettingValue.Int(value));
public void SetFloat(string key, float value) => Set(key, SettingValue.Float(value));
public void SetString(string key, string value) => Set(key, SettingValue.String(value));
/// <summary>
/// Изменяет настройку извне (с проверкой флага exposed)
/// </summary>
public bool SetExternal(string key, SettingValue value)
{
if (this == null) return false;
SettingEntry def = Define(key);
if (def == null)
{
Debug.LogWarning($"[SettingsManager] No definition found for key '{key}'.");
return false;
}
if (!def.exposed)
{
Debug.LogWarning($"[SettingsManager] Setting '{key}' is not exposed for external changes. Set 'exposed' flag to true in SettingsDatabase.");
return false;
}
Set(key, value);
return true;
}
// Удобные типизированные сеттеры для внешних изменений
public bool SetExternalBool(string key, bool value) => SetExternal(key, SettingValue.Bool(value));
public bool SetExternalInt(string key, int value) => SetExternal(key, SettingValue.Int(value));
public bool SetExternalFloat(string key, float value) => SetExternal(key, SettingValue.Float(value));
public bool SetExternalString(string key, string value) => SetExternal(key, SettingValue.String(value));
/// <summary>
/// Немедленное сохранение настроек
/// </summary>
public void SaveSettings()
{
SaveNow();
}
/// <summary>
/// Перезагружает настройки из файла (только exposed настройки)
/// Полезно для hot-reload при изменении файла извне
/// </summary>
public void ReloadFromFile()
{
if (!File.Exists(path))
{
Debug.LogWarning($"[SettingsManager] Settings file not found: {path}");
return;
}
try
{
string json = File.ReadAllText(path);
SettingsSaveData data = JsonUtility.FromJson<SettingsSaveData>(json);
if (data != null)
{
foreach (SettingsSaveData.Entry e in data.entries)
{
if (string.IsNullOrWhiteSpace(e.key)) continue;
SettingEntry def = Define(e.key);
if (def == null)
{
Debug.LogWarning($"[SettingsManager] Unknown setting key '{e.key}' in file.");
continue;
}
// Проверяем флаг exposed
if (!def.exposed)
{
Debug.LogWarning($"[SettingsManager] Skipping '{e.key}' - not exposed for external changes.");
continue;
}
// Конвертируем из компактного формата и применяем настройку
SettingValue value = SettingValue.FromCompact(e.value);
SettingValue clean = def.Sanitize(value);
map[e.key] = clean;
SettingsApplier.Apply(def, clean);
OnSettingChanged?.Invoke(e.key, clean);
}
}
}
catch (Exception ex)
{
Debug.LogError($"[SettingsManager] Failed to reload settings: {ex.Message}");
}
}
public void ApplyAll()
{
if (!database) return;
foreach (SettingEntry def in database.entries)
{
if (def == null || string.IsNullOrWhiteSpace(def.key)) continue;
SettingsApplier.Apply(def, Get(def.key));
}
}
public void ResetAllToDefaults()
{
if (!database) return;
foreach (SettingEntry def in database.entries)
{
if (def == null || string.IsNullOrWhiteSpace(def.key)) continue;
map[def.key] = def.DefaultValue;
SettingsApplier.Apply(def, def.DefaultValue);
}
ScheduleSave(0.05f);
}
/// <summary>
/// Сбрасывает настройки указанной группы к значениям по умолчанию
/// </summary>
/// <param name="groupName">Имя группы настроек</param>
public void ResetGroupToDefaults(string groupName)
{
if (!database || string.IsNullOrWhiteSpace(groupName)) return;
foreach (SettingEntry def in database.entries)
{
if (def == null || string.IsNullOrWhiteSpace(def.key)) continue;
if (!string.Equals(def.group, groupName, StringComparison.OrdinalIgnoreCase)) continue;
map[def.key] = def.DefaultValue;
SettingsApplier.Apply(def, def.DefaultValue);
}
ScheduleSave(0.05f);
}
private void LoadOrDefaults()
{
LoadOrDefaults(checkExposed: false);
}
private void LoadOrDefaults(bool checkExposed)
{
map.Clear();
if (File.Exists(path))
{
try
{
string json = File.ReadAllText(path);
SettingsSaveData data = JsonUtility.FromJson<SettingsSaveData>(json);
if (data != null)
{
foreach (SettingsSaveData.Entry e in data.entries)
{
if (string.IsNullOrWhiteSpace(e.key)) continue;
// Получаем определение
SettingEntry def = Define(e.key);
// Если checkExposed = true, проверяем флаг exposed
if (checkExposed)
{
if (def != null && !def.exposed)
{
Debug.LogWarning($"[SettingsManager] Skipping '{e.key}' from file - not exposed.");
continue;
}
}
// Конвертируем из компактного формата
SettingValue value = SettingValue.FromCompact(e.value);
// Применяем Sanitize если есть определение
if (def != null)
{
map[e.key] = def.Sanitize(value);
}
else
{
map[e.key] = value;
}
}
}
}
catch
{
// если файл битый — просто упадём на дефолты ниже
}
}
if (!database) return;
foreach (SettingEntry def in database.entries)
{
if (def == null || string.IsNullOrWhiteSpace(def.key)) continue;
if (!map.ContainsKey(def.key))
map[def.key] = def.DefaultValue;
else
map[def.key] = def.Sanitize(map[def.key]);
}
OnSettingsLoaded?.Invoke();
}
private void ScheduleSave(float delay)
{
// Проверяем, что объект не уничтожен
if (this == null || !gameObject) return;
if (saveRoutine != null) StopCoroutine(saveRoutine);
saveRoutine = StartCoroutine(SaveDelayed(delay));
}
private IEnumerator SaveDelayed(float delay)
{
yield return new WaitForSecondsRealtime(delay);
SaveNow();
saveRoutine = null;
}
private void SaveNow()
{
SettingsSaveData data = new();
// Конвертируем значения в компактный формат
foreach (var kv in map)
{
data.entries.Add(new SettingsSaveData.Entry
{
key = kv.Key,
value = kv.Value.ToCompact() // Конвертация в компактный формат
});
}
try
{
File.WriteAllText(path, JsonUtility.ToJson(data, true));
OnSettingsSaved?.Invoke();
}
catch (Exception e)
{
Debug.LogWarning($"[SettingsManager] Save failed: {e.Message}");
}
}
private void OnApplicationQuit() => SaveNow();
}
}