using System; using System.Collections.Generic; using UnityEngine; namespace RR.Common.Settings { public enum SettingValueType { Bool, Int, Float, String, Button, // Кнопка - не хранит значение, только вызывает действие OptionPicker // Выбор из списка опций (enum-like) } public enum ApplyTarget { None, Audio // Применить к звуку через AudioManager (музыка или эффекты) } public enum AudioTargetType { Music, // Музыка Sfx // Звуковые эффекты } public enum IntDisplayFormat { Slider, // Обычный слайдер ValueSelector // Выбор значения (со стрелками или дропдауном) } public enum FloatDisplayFormat { Slider, // Обычный слайдер ValueSelector // Выбор значения (со стрелками) } public enum OptionPickerDisplayFormat { Default, // Текущее поведение: dropdown или отдельный option picker prefab ValueSelector // Выбор значения через тот же prefab, что и для Int/Float } [Serializable] public struct SettingValue { public SettingValueType type; public bool b; public int i; public float f; public string s; public static SettingValue Bool(bool v) => new() { type = SettingValueType.Bool, b = v }; public static SettingValue Int(int v) => new() { type = SettingValueType.Int, i = v }; public static SettingValue Float(float v) => new() { type = SettingValueType.Float, f = v }; public static SettingValue String(string v) => new() { type = SettingValueType.String, s = v }; public static SettingValue OptionPicker(int v) => new() { type = SettingValueType.OptionPicker, i = v }; /// /// Создает компактную версию для сериализации (только нужные поля) /// public SettingValueCompact ToCompact() { return new SettingValueCompact { type = (int)type, value = type switch { SettingValueType.Bool => b ? "1" : "0", SettingValueType.Int => i.ToString(), SettingValueType.OptionPicker => i.ToString(), SettingValueType.Float => f.ToString("F6"), SettingValueType.String => s ?? "", _ => "" } }; } /// /// Создает SettingValue из компактной версии /// public static SettingValue FromCompact(SettingValueCompact compact) { var type = (SettingValueType)compact.type; return type switch { SettingValueType.Bool => Bool(compact.value == "1"), SettingValueType.Int => Int(int.TryParse(compact.value, out int i) ? i : 0), SettingValueType.OptionPicker => OptionPicker(int.TryParse(compact.value, out int optionIndex) ? optionIndex : 0), SettingValueType.Float => Float(float.TryParse(compact.value, out float f) ? f : 0f), SettingValueType.String => String(compact.value ?? ""), _ => default }; } } /// /// Компактная версия SettingValue для сериализации (экономит место в JSON) /// [Serializable] public struct SettingValueCompact { public int type; public string value; } [Serializable] public class SettingEntry { public string key; public string displayName; [TextArea] public string description; public string group = "General"; [Tooltip("Показывать ли настройку в UI меню настроек")] public bool shownInSettings = true; [Tooltip("Доступна ли настройка для изменения извне (через API/файл)")] public bool exposed = false; public SettingValueType valueType; // Defaults public bool defaultBool = true; public int defaultInt = 0; public float defaultFloat = 1f; public string defaultString = ""; // Ranges (used for Int/Float) public int minInt = 0; public int maxInt = 10; [Tooltip("Формат отображения для Int (слайдер или выбор значения)")] public IntDisplayFormat intDisplayFormat = IntDisplayFormat.Slider; [Tooltip("Шаг для выбора значения Int (используется только для ValueSelector)")] public int intStep = 1; public float minFloat = 0f; public float maxFloat = 1f; [Tooltip("Отображать float значение как проценты (0-100%)")] public bool displayAsPercentage = false; [Tooltip("Формат отображения для Float (слайдер или выбор значения)")] public FloatDisplayFormat floatDisplayFormat = FloatDisplayFormat.Slider; [Tooltip("Шаг для выбора значения Float (используется только для ValueSelector)")] public float floatStep = 0.1f; public ApplyTarget applyTarget = ApplyTarget.None; // Audio params (используется только для ApplyTarget.Audio) [Tooltip("Тип звука: музыка или звуковые эффекты")] public AudioTargetType audioTargetType = AudioTargetType.Music; // Button params (для типа Button) [Tooltip("Текст на кнопке (если не задан displayName)")] public string buttonText; [Tooltip("Дополнительные данные для кнопки (URL, имя сцены и т.д.)")] public string buttonData; [Tooltip("Цвет кнопки")] public Color buttonColor = Color.white; [Tooltip("Текстура/спрайт кнопки")] public Sprite buttonSprite; // OptionPicker params (для типа OptionPicker) [Tooltip("Список вариантов выбора")] public List options; [Tooltip("Индекс варианта по умолчанию")] public int defaultOptionIndex; [Tooltip("Формат отображения для Option Picker")] public OptionPickerDisplayFormat optionPickerDisplayFormat = OptionPickerDisplayFormat.Default; public SettingValue DefaultValue => valueType switch { SettingValueType.Bool => SettingValue.Bool(defaultBool), SettingValueType.Int => SettingValue.Int(defaultInt), SettingValueType.Float => SettingValue.Float(defaultFloat), SettingValueType.String => SettingValue.String(defaultString ?? ""), SettingValueType.Button => default, // Кнопки не хранят значение SettingValueType.OptionPicker => SettingValue.OptionPicker(options != null && options.Count > 0 ? Mathf.Clamp(defaultOptionIndex, 0, options.Count - 1) : 0), _ => default }; public SettingValue Sanitize(SettingValue v) { if (valueType == SettingValueType.OptionPicker) { if (v.type != SettingValueType.OptionPicker && v.type != SettingValueType.Int) return DefaultValue; } else if (v.type != valueType) { return DefaultValue; } // защита от кривых min/max int iMin = Mathf.Min(minInt, maxInt); int iMax = Mathf.Max(minInt, maxInt); float fMin = Mathf.Min(minFloat, maxFloat); float fMax = Mathf.Max(minFloat, maxFloat); return valueType switch { SettingValueType.Bool => SettingValue.Bool(v.b), SettingValueType.Int => SettingValue.Int(Mathf.Clamp(v.i, iMin, iMax)), SettingValueType.Float => SettingValue.Float(Mathf.Clamp(v.f, fMin, fMax)), SettingValueType.String => SettingValue.String(v.s ?? ""), SettingValueType.Button => default, SettingValueType.OptionPicker => SettingValue.OptionPicker(options != null && options.Count > 0 ? Mathf.Clamp(v.i, 0, options.Count - 1) : 0), _ => DefaultValue }; } } [CreateAssetMenu(menuName = "Settings/Database")] public class SettingsDatabase : ScriptableObject { public List entries = new(); public SettingEntry Find(string key) => entries.Find(e => string.Equals(e.key, key, StringComparison.Ordinal)); private void OnValidate() { var set = new HashSet(); foreach (var e in entries) { if (e == null) continue; if (string.IsNullOrWhiteSpace(e.key)) Debug.LogWarning($"[SettingsDatabase] Empty key in {name}", this); else if (!set.Add(e.key)) Debug.LogWarning($"[SettingsDatabase] Duplicate key '{e.key}' in {name}", this); } } } }