445 lines
15 KiB
C#
445 lines
15 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
public class Setting : IViewStringSupplier
|
||
{
|
||
#region FIELDS
|
||
//general
|
||
public string Id { get; private set; }
|
||
public string Alias { get; private set; }
|
||
public string Group { get; private set; }
|
||
public SettingType Type { get; private set; }
|
||
public bool Cycling { get; private set; }
|
||
|
||
//type: Bool
|
||
public bool BoolValue { get; private set; }
|
||
|
||
//type: Int
|
||
public int IntValue { get; private set; }
|
||
public int IntMin { get; private set; }
|
||
public int IntMax { get; private set; }
|
||
public int IntStep { get; private set; }
|
||
|
||
//type: Float
|
||
public float FloatValue { get; private set; }
|
||
public float FloatMin { get; private set; }
|
||
public float FloatMax { get; private set; }
|
||
public float FloatStep { get; private set; }
|
||
|
||
//type: String
|
||
public string StringValue { get; set; }
|
||
public List<string> AllowedValues { get; private set; }
|
||
#endregion
|
||
|
||
#region CONSTRUCTORS
|
||
/// <summary>
|
||
/// Базовый конструктор
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Bool конструктор
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Int конструктор
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Float конструктор
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// String конструктор
|
||
/// </summary>
|
||
public Setting(string id, string alias, string group, SettingType type, bool cycling, string value, List<string> 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<string> 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
|
||
}
|