using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(fileName = "Setting", menuName = "Settings/Setting")] [System.Serializable] public class Setting : ScriptableObject { #region FIELDS //general [Header("General")] public SettingType Type; public string Id; public string Alias; [TextArea] public string Description; //type: Bool public bool BoolValue; //type: Int public int IntValue; public int IntMin; public int IntMax; public int IntStep; //type: Float public float FloatValue; public float FloatMin; public float FloatMax; public float FloatStep; //type: String public string StringValue; public List AllowedValues; #endregion #region CONSTRUCTORS /// /// Базовый конструктор /// private Setting(string id, string alias, SettingType type) { this.Id = id; this.Alias = alias; this.Type = type; } /// /// Bool конструктор /// public Setting(string id, string alias, SettingType type, bool value) : this(id, alias, type) { //Валидация типа CheckType(type, SettingType.Bool, id); BoolValue = value; } /// /// Int конструктор /// public Setting(string id, string alias, SettingType type, int value, int min, int max, int step) : this(id, alias, type) { CheckType(type, SettingType.Int, id); ValidateInt(value: ref value, min: ref min, max: ref max, step: ref step, settingId: id); this.IntValue = value; this.IntMin = min; this.IntMax = max; this.IntStep = step; } /// /// Float конструктор /// public Setting(string id, string alias, SettingType type, float value, float min, float max, float step) : this(id, alias, type) { CheckType(type, SettingType.Float, id); ValidateFloat(value: ref value, min: ref min, max: ref max, step: ref step, settingId: id); this.FloatValue = value; this.FloatMin = min; this.FloatMax = max; this.FloatStep = step; } /// /// String конструктор /// public Setting(string id, string alias, SettingType type, string value, List allowedValues) : this(id, alias, type) { CheckType(type, SettingType.String, id); ValidateString(ref value, allowedValues, id); this.AllowedValues = allowedValues; this.StringValue = value; } #endregion #region HELPERS private void CheckType(SettingType type, SettingType expectedType, string id) { if (type != expectedType) throw new System.Exception($"Настройка {id}. Несоответствие типов."); } private void ValidateInt(ref int value, ref int min, ref int max, ref int step, string settingId) { bool hasWarning = false; string WarningText = string.Empty; if (min > max) { hasWarning = true; WarningText += $"Максимум ({max}) ниже минимума ({min}). Минимум и максимум поменяны местами.\n"; (min, max) = (max, min); } if (value < min) { hasWarning = true; WarningText += $"Значение {value} ниже минимума {min}. Установлено значение {min}.\n"; value = min; } if (value > max) { hasWarning = true; WarningText += $"Значение {value} выше максимума {max}. Установлено значение {max}.\n"; value = max; } int delta = max - min; if (step > delta) { hasWarning = true; WarningText += $"Шаг ({step}) установлен выше чем разница максимума и минимума ({delta}). Шаг установлен в значение {delta}.\n"; step = delta; } if (step < 0) { hasWarning = true; WarningText += $"Шаг ({step}) не может быть отрицательным. Значение инвертировано ({-step}).\n"; step = -step; } if (hasWarning) Debug.LogWarning($"Настройка {settingId}:\n" + WarningText); } private void ValidateFloat(ref float value, ref float min, ref float max, ref float step, string settingId) { bool hasWarning = false; string WarningText = string.Empty; if (min > max) { hasWarning = true; WarningText += $"Максимум ({max}) ниже минимума ({min}). Минимум и максимум поменяны местами.\n"; (min, max) = (max, min); } if (value < min) { hasWarning = true; WarningText += $"Значение {value} ниже минимума {min}. Установлено значение {min}.\n"; value = min; } if (value > max) { hasWarning = true; WarningText += $"Значение {value} выше максимума {max}. Установлено значение {max}.\n"; value = max; } float delta = max - min; if (step > delta) { hasWarning = true; WarningText += $"Шаг ({step}) установлен выше чем разница максимума и минимума ({delta}). Шаг установлен в значение {delta}.\n"; step = delta; } if (step < 0) { hasWarning = true; WarningText += $"Шаг ({step}) не может быть отрицательным. Значение инвертировано ({-step}).\n"; step = -step; } if (hasWarning) Debug.LogWarning($"Настройка {settingId}:\n" + WarningText); } private void ValidateString(ref string value, List allowedValues, string settingId) { if (allowedValues == null) { Debug.LogWarning($"Настройка {settingId}:\nСписок значений null"); } else if (allowedValues.Count == 0) { Debug.LogWarning($"Настройка {settingId}:\nСписок значений пуст"); } else if (!allowedValues.Contains(value)) { string newValue = allowedValues[allowedValues.Count / 2]; Debug.LogWarning($"Настройка {settingId}:\nСписок значений не содержит установленного значения{value}. Установлено среднее значение {newValue}."); value = newValue; } } #endregion #region CONTROLS //Возвращает изменилось ли значение public bool ChangeValue(int direction) => Type switch { SettingType.Bool => ChangeBool(direction), SettingType.Int => ChangeInt(direction), SettingType.Float => ChangeFloat(direction), SettingType.String => ChangeString(direction), _ => false }; private bool ChangeBool(int direction) { if (direction == 0) return false; else { bool newValue = direction > 0; if (newValue == BoolValue) return false; BoolValue = newValue; return true; } } private bool ChangeInt(int direction) { if (direction == 0) return false; int sign = Mathf.Clamp(direction, -1, 1); int newValue = IntValue + IntStep * sign; newValue = Mathf.Clamp(newValue, IntMin, IntMax); if (IntValue == newValue) return false; IntValue = newValue; return true; } private bool ChangeFloat(int direction) { if (direction == 0) return false; float sign = Mathf.Sign(direction); float newValue = FloatValue + FloatStep * sign; newValue = Mathf.Clamp(newValue, FloatMin, FloatMax); // Сравнение float с учетом погрешности if (Mathf.Abs(FloatValue - newValue) < float.Epsilon) return false; FloatValue = newValue; return true; } private bool ChangeString(int direction) { if (direction == 0 || AllowedValues == null || AllowedValues.Count == 0) return false; int sign = direction > 0 ? 1 : -1; int currentIndex = AllowedValues.IndexOf(StringValue); // Если значение не в списке if (currentIndex < 0) { return false; } int newIndex = currentIndex + sign; int maxIndex = AllowedValues.Count - 1; newIndex = Mathf.Clamp(newIndex, 0, maxIndex); if (currentIndex == newIndex) return false; StringValue = AllowedValues[newIndex]; return true; } #endregion #region SERIALIZATION //Преобразовать класс в DTO public SettingData ToSerializableData() { return new SettingData { Id = this.Id, Alias = this.Alias, Type = this.Type, BoolValue = this.BoolValue, IntValue = this.IntValue, FloatValue = this.FloatValue, StringValue = this.StringValue, IntMin = this.IntMin, IntMax = this.IntMax, IntStep = this.IntStep, FloatMin = this.FloatMin, FloatMax = this.FloatMax, FloatStep = this.FloatStep, AllowedValues = this.AllowedValues }; } //Восстановить класс из DTO public static Setting FromSerializableData(SettingData data) { return data.Type switch { SettingType.Bool => new Setting(id: data.Id, alias: data.Alias, type: data.Type, value: data.BoolValue), SettingType.Int => new Setting(id: data.Id, alias: data.Alias, type: data.Type, value: data.IntValue, min: data.IntMin, max: data.IntMax, step: data.IntStep), SettingType.Float => new Setting(id: data.Id, alias: data.Alias, type: data.Type, value: data.FloatValue, min: data.FloatMin, max: data.FloatMax, step: data.FloatStep), SettingType.String => new Setting(id: data.Id, alias: data.Alias, type: data.Type, value: data.StringValue, allowedValues: data.AllowedValues), _ => null }; } public string GetStringForView() { return Type switch { SettingType.Bool => BoolValue.ToString(), SettingType.Int => IntValue.ToString(), SettingType.Float => FloatValue.ToString(), SettingType.String => StringValue, _ => null }; } #endregion public enum SettingType { Int, Float, String, Bool } }