using System.Collections.Generic; using UnityEngine; public class Setting : IViewStringSupplier { #region FIELDS //general public string Id { get; set; } public string Alias { get; set; } public string Group { get; set; } public SettingType Type { get; set; } public bool Cycling { get; set; } //type: Bool public bool BoolValue { get; set; } //type: Int public int IntValue { get; set; } public int IntMin { get; set; } public int IntMax { get; set; } public int IntStep { get; set; } //type: Float public float FloatValue { get; set; } public float FloatMin { get; set; } public float FloatMax { get; set; } public float FloatStep { get; set; } //type: String public string StringValue { get; set; } public List AllowedValues { get; private set; } #endregion #region CONSTRUCTORS /// /// Базовый конструктор /// private Setting(string id, string alias, string group, SettingType type, bool cycling) { this.Id = id; this.Alias = alias; this.Group = group; this.Type = type; this.Cycling = cycling; } /// /// Bool конструктор /// public Setting(string id, string alias, string group, SettingType type, bool value, bool cycling) : this(id, alias, group, type, cycling) { //Валидация типа CheckType(type, SettingType.Bool, id); BoolValue = value; } /// /// Int конструктор /// public Setting(string id, string alias, string group, SettingType type, bool cycling, int value, int min, int max, int step) : this(id, alias, group, type, cycling) { 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, string group, SettingType type, bool cycling, float value, float min, float max, float step) : this(id, alias, group, type, cycling) { 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, string group, SettingType type, bool cycling, string value, List allowedValues) : this(id, alias, group, type, cycling) { 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; if (Cycling) { BoolValue = !BoolValue; return true; } 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; if (Cycling) { int range = IntMax - IntMin + 1; if (newValue > IntMax) newValue = IntMin + (newValue - IntMax - 1) % range; else if (newValue < IntMin) newValue = IntMax - (IntMin - newValue - 1) % range; } else { 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; if (Cycling) { float range = FloatMax - FloatMin; if (range <= 0) { // Если диапазон нулевой, просто возвращаем минимальное значение newValue = FloatMin; } else if (newValue > FloatMax) { // Для циклического перехода через максимум float overflow = newValue - FloatMax; newValue = FloatMin + overflow % range; } else if (newValue < FloatMin) { // Для циклического перехода через минимум float underflow = FloatMin - newValue; newValue = FloatMax - underflow % range; } } else { 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; if (Cycling) { // Циклический переход if (newIndex > maxIndex) newIndex = 0; else if (newIndex < 0) newIndex = maxIndex; } else { // Без циклического режима - ограничение границами 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, Group = this.Group, Type = this.Type, Cycling = this.Cycling, 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, group: data.Group, type: data.Type, cycling:data.Cycling, value: data.BoolValue), SettingType.Int => new Setting(id: data.Id, alias: data.Alias, group: data.Group, type: data.Type, cycling: data.Cycling, value: data.IntValue, min: data.IntMin, max: data.IntMax, step: data.IntStep), SettingType.Float=> new Setting(id: data.Id, alias: data.Alias, group: data.Group, type: data.Type, cycling: data.Cycling, value: data.FloatValue, min: data.FloatMin, max: data.FloatMax, step: data.FloatStep), SettingType.String=>new Setting(id: data.Id, alias: data.Alias, group: data.Group, type: data.Type, cycling: data.Cycling, 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 }