Added new settings in settings system
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled

- Добавил больше настроек в систему настроек
- Пока у большинства настроек нет функционала
- Немного изменил внешний вид настроек
This commit is contained in:
Данил Ярош 2026-02-06 08:43:51 +03:00
parent 4d05d1adbb
commit 24aaf18d4b
13 changed files with 8754 additions and 10133 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -138,7 +138,7 @@ MonoBehaviour:
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 600
m_PreferredHeight: 50
m_PreferredHeight: 90
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
@ -177,7 +177,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 300, y: -50}
m_AnchoredPosition: {x: 300, y: -90}
m_SizeDelta: {x: 600, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &8196548391682863322
@ -277,7 +277,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 600, y: 50}
m_SizeDelta: {x: 600, y: 90}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &7434999243356666657
MonoBehaviour:
@ -389,8 +389,8 @@ MonoBehaviour:
m_Calls: []
m_text: "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontAsset: {fileID: 11400000, guid: 5302535af1044152a457ed104f1f4b91, type: 2}
m_sharedMaterial: {fileID: 2164040, guid: 5302535af1044152a457ed104f1f4b91, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
@ -414,12 +414,12 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 24
m_fontSize: 48
m_fontSizeBase: 24
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_enableAutoSizing: 1
m_fontSizeMin: 24
m_fontSizeMax: 48
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512

View File

@ -37,7 +37,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 200, y: 50}
m_SizeDelta: {x: 400, y: 70}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6418208233271057322
CanvasRenderer:
@ -62,7 +62,7 @@ MonoBehaviour:
m_IgnoreLayout: 0
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: 200
m_PreferredWidth: 400
m_PreferredHeight: 70
m_FlexibleWidth: -1
m_FlexibleHeight: -1
@ -182,12 +182,12 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 36.35
m_fontSize: 48
m_fontSizeBase: 48
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontSizeMax: 48
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512

View File

@ -7346,7 +7346,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.132, y: 0.5}
m_AnchorMax: {x: 0.846, y: 0.77500004}
m_AnchoredPosition: {x: -54.500015, y: 5}
m_AnchoredPosition: {x: -54.5, y: 5}
m_SizeDelta: {x: -107, y: -10}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1474919456
@ -7717,18 +7717,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 98f414c07d2f47e794d25468e8519ba7, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::RR.Common.UI.SettingsUIList
settingsList: []
musicVolumeSlider: {fileID: 0}
sfxVolumeSlider: {fileID: 0}
freeGameToggle: {fileID: 0}
givingPrizesToggle: {fileID: 0}
startLivesSlider: {fileID: 0}
increasedLivesSlider: {fileID: 0}
statsButton: {fileID: 0}
volumePercentageText: {fileID: 0}
sfxVolumePercentageText: {fileID: 0}
startLivesCountText: {fileID: 0}
increasedLivesCountText: {fileID: 0}
--- !u!114 &1537614491
MonoBehaviour:
m_ObjectHideFlags: 0
@ -9291,7 +9279,8 @@ MonoBehaviour:
groupPanelPrefab: {fileID: 2186865876444500735, guid: 1983dbab136fb73419f146d56772a8c8, type: 3}
defaultGroupsExpanded: 1
rememberGroupFoldout: 1
groupOrder: []
groupOrder:
- "\u041C\u0443\u0437\u044B\u043A\u0430 \u0438 \u0437\u0432\u0443\u043A\u0438"
sortByNameInsideGroup: 1
toggleRowPrefab: {fileID: -3497382869404314779, guid: 3f18baed1f4d89f4ab9b4caf0c789a38, type: 3}
sliderRowPrefab: {fileID: 5445087308684328405, guid: a85153c0090994b438ad050a1dbcc306, type: 3}
@ -9443,7 +9432,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0.00077443453}
m_AnchoredPosition: {x: 0, y: -0.000034523957}
m_SizeDelta: {x: 0, y: 300}
m_Pivot: {x: 0, y: 1}
--- !u!114 &1792218764
@ -10209,7 +10198,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.132, y: 0.21750003}
m_AnchorMax: {x: 0.846, y: 0.5}
m_AnchoredPosition: {x: -54.500015, y: -79.00003}
m_AnchoredPosition: {x: -54.5, y: -79.00003}
m_SizeDelta: {x: -107, y: -63}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1959311160

View File

@ -5,6 +5,9 @@ using RR.Common.Settings;
namespace RR.Common.InspectorOverrides
{
/// <summary>
/// Custom property drawer for SettingEntry - v1.2
/// </summary>
[CustomPropertyDrawer(typeof(SettingEntry))]
public class SettingEntryDrawer : PropertyDrawer
{
@ -24,6 +27,8 @@ namespace RR.Common.InspectorOverrides
var nameProp = property.FindPropertyRelative("displayName");
var descProp = property.FindPropertyRelative("description");
var groupProp = property.FindPropertyRelative("group");
var shownInSettingsProp = property.FindPropertyRelative("shownInSettings");
var exposedProp = property.FindPropertyRelative("exposed");
var typeProp = property.FindPropertyRelative("valueType");
var defBoolProp = property.FindPropertyRelative("defaultBool");
@ -33,14 +38,14 @@ namespace RR.Common.InspectorOverrides
var minIntProp = property.FindPropertyRelative("minInt");
var maxIntProp = property.FindPropertyRelative("maxInt");
var intDisplayFormatProp = property.FindPropertyRelative("intDisplayFormat");
var intStepProp = property.FindPropertyRelative("intStep");
var minFloatProp = property.FindPropertyRelative("minFloat");
var maxFloatProp = property.FindPropertyRelative("maxFloat");
var displayAsPercentageProp = property.FindPropertyRelative("displayAsPercentage");
var applyProp = property.FindPropertyRelative("applyTarget");
var mixerProp = property.FindPropertyRelative("mixer");
var mixerParamProp = property.FindPropertyRelative("mixerParameter");
var minDbProp = property.FindPropertyRelative("minDb");
var maxDbProp = property.FindPropertyRelative("maxDb");
var audioTargetProp = property.FindPropertyRelative("audioTargetType");
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
@ -69,6 +74,9 @@ namespace RR.Common.InspectorOverrides
DrawProp(ref r, keyProp, "Key");
DrawProp(ref r, groupProp, "Group");
DrawProp(ref r, descProp, "Description", includeChildren: true);
DrawSectionTitle(ref r, "Visibility & Access");
DrawFlagsRow(ref r, shownInSettingsProp, exposedProp, "Shown in UI", "Exposed");
DrawSectionTitle(ref r, "Value");
DrawProp(ref r, typeProp, "Value Type");
@ -84,11 +92,20 @@ namespace RR.Common.InspectorOverrides
case SettingValueType.Int:
DrawProp(ref r, defIntProp, "Default");
DrawMinMaxRow(ref r, minIntProp, maxIntProp, "Min", "Max");
DrawProp(ref r, intDisplayFormatProp, "Display Format");
// Показываем intStep только если выбран ValueSelector
var displayFormat = (IntDisplayFormat)intDisplayFormatProp.enumValueIndex;
if (displayFormat == IntDisplayFormat.ValueSelector)
{
DrawProp(ref r, intStepProp, "Step");
}
break;
case SettingValueType.Float:
DrawProp(ref r, defFloatProp, "Default");
DrawMinMaxRow(ref r, minFloatProp, maxFloatProp, "Min", "Max");
DrawProp(ref r, displayAsPercentageProp, "Display as %");
break;
case SettingValueType.String:
@ -122,11 +139,9 @@ namespace RR.Common.InspectorOverrides
DrawProp(ref r, applyProp, "Apply Target");
var apply = (ApplyTarget)applyProp.enumValueIndex;
if (apply == ApplyTarget.AudioMixerFloat01ToDb)
if (apply == ApplyTarget.Audio)
{
DrawProp(ref r, mixerProp, "Audio Mixer");
DrawProp(ref r, mixerParamProp, "Exposed Param");
DrawMinMaxRow(ref r, minDbProp, maxDbProp, "Min dB", "Max dB");
DrawProp(ref r, audioTargetProp, "Audio Type");
}
// Warning
@ -172,6 +187,10 @@ namespace RR.Common.InspectorOverrides
h += HeightOf(keyProp) + space;
h += HeightOf(groupProp) + space;
h += HeightOf(descProp, true) + space;
// Visibility & Access section title
h += line + space;
h += line + space; // flags row (shownInSettings + exposed)
// Value section title
h += line + space;
@ -188,9 +207,26 @@ namespace RR.Common.InspectorOverrides
break;
case SettingValueType.Int:
h += line + space; // Default
h += line + space; // range row
h += line + space; // intDisplayFormat
// Добавляем высоту для intStep если выбран ValueSelector
var intDisplayFormatProp = property.FindPropertyRelative("intDisplayFormat");
if (intDisplayFormatProp != null)
{
var displayFormat = (IntDisplayFormat)intDisplayFormatProp.enumValueIndex;
if (displayFormat == IntDisplayFormat.ValueSelector)
{
h += line + space; // intStep
}
}
break;
case SettingValueType.Float:
h += line + space; // Default
h += line + space; // range row
h += line + space; // displayAsPercentage checkbox
break;
case SettingValueType.Button:
@ -214,11 +250,9 @@ namespace RR.Common.InspectorOverrides
h += HeightOf(applyProp) + space;
ApplyTarget apply = (ApplyTarget)applyProp.enumValueIndex;
if (apply == ApplyTarget.AudioMixerFloat01ToDb)
if (apply == ApplyTarget.Audio)
{
h += line + space; // mixer
h += line + space; // param
h += line + space; // db row
h += line + space; // audioTargetType
}
if (string.IsNullOrWhiteSpace(keyProp.stringValue))
@ -267,6 +301,28 @@ namespace RR.Common.InspectorOverrides
r.y += line + space;
}
private static void DrawFlagsRow(ref Rect r, SerializedProperty leftProp, SerializedProperty rightProp, string leftLabel, string rightLabel)
{
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
float gap = 10f;
float half = (r.width - gap) * 0.5f;
Rect left = new(r.x, r.y, half, line);
Rect right = new(r.x + half + gap, r.y, half, line);
// Shown in Settings
GUIContent leftContent = new GUIContent(leftLabel, "Показывать настройку в UI меню настроек");
EditorGUI.PropertyField(left, leftProp, leftContent);
// Exposed
GUIContent rightContent = new GUIContent(rightLabel, "Доступна для изменения извне (через API/файл)");
EditorGUI.PropertyField(right, rightProp, rightContent);
r.y += line + space;
}
private static string BuildHeader(string displayName, string key)
{

View File

@ -12,99 +12,30 @@ namespace RR.Common.Settings
case ApplyTarget.None:
return;
case ApplyTarget.AudioMixerFloat01ToDb:
ApplyAudioMixer(def, v);
return;
case ApplyTarget.QualityLevel:
ApplyQuality(v.i);
return;
case ApplyTarget.VSync:
ApplyVSync(v.b);
return;
case ApplyTarget.TargetFps:
ApplyTargetFps(v.i);
return;
case ApplyTarget.Fullscreen:
ApplyFullscreen(v.b);
return;
case ApplyTarget.FullscreenMode:
ApplyFullscreenMode(v.i);
return;
case ApplyTarget.ResolutionIndex:
ApplyResolutionIndex(v.i);
case ApplyTarget.Audio:
ApplyAudio(def, v);
return;
}
}
private static void ApplyAudioMixer(SettingEntry def, SettingValue v)
private static void ApplyAudio(SettingEntry def, SettingValue v)
{
if (string.IsNullOrWhiteSpace(def.mixerParameter)) return;
float volume = Mathf.Clamp01(v.f);
// Применяем через AudioManager
if (GameManager.Instance?.Audio != null)
{
// Определяем тип звука по имени параметра
string param = def.mixerParameter.ToLower();
if (param.Contains("music"))
switch (def.audioTargetType)
{
GameManager.Instance.Audio.SetMusicVolume(volume);
}
else if (param.Contains("sfx") || param.Contains("sound") || param.Contains("effect"))
{
GameManager.Instance.Audio.SetSfxVolume(volume);
case AudioTargetType.Music:
GameManager.Instance.Audio.SetMusicVolume(volume);
break;
case AudioTargetType.Sfx:
GameManager.Instance.Audio.SetSfxVolume(volume);
break;
}
}
}
private static void ApplyQuality(int level)
{
var names = QualitySettings.names;
if (names == null || names.Length == 0) return;
level = Mathf.Clamp(level, 0, names.Length - 1);
QualitySettings.SetQualityLevel(level, true);
}
private static void ApplyVSync(bool enabled)
{
QualitySettings.vSyncCount = enabled ? 1 : 0;
}
private static void ApplyTargetFps(int fps)
{
Application.targetFrameRate = fps > 0 ? fps : -1;
}
private static void ApplyFullscreen(bool fullscreen)
{
Screen.fullScreen = fullscreen;
}
private static void ApplyFullscreenMode(int idx)
{
// FullScreenMode: 0..3 (ExclusiveFullScreen, FullScreenWindow, MaximizedWindow, Windowed)
idx = Mathf.Clamp(idx, 0, 3);
Screen.fullScreenMode = (FullScreenMode)idx;
}
private static void ApplyResolutionIndex(int index)
{
var rs = Screen.resolutions;
if (rs == null || rs.Length == 0) return;
index = Mathf.Clamp(index, 0, rs.Length - 1);
var r = rs[index];
// без герцовки — самый совместимый вариант
Screen.SetResolution(r.width, r.height, Screen.fullScreenMode);
}
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
namespace RR.Common.Settings
{
@ -15,21 +14,22 @@ namespace RR.Common.Settings
OptionPicker // Выбор из списка опций (enum-like)
}
// "Куда применять" — расширяется редко (не под каждую настройку)
public enum ApplyTarget
{
None,
// Audio
AudioMixerFloat01ToDb, // float 0..1 -> dB и в AudioMixer parameter
// Graphics / Performance
QualityLevel, // int -> QualitySettings.SetQualityLevel
VSync, // bool -> QualitySettings.vSyncCount
TargetFps, // int -> Application.targetFrameRate
Fullscreen, // bool -> Screen.fullScreen
FullscreenMode, // int 0..3 -> Screen.fullScreenMode
ResolutionIndex // int -> индекс в Screen.resolutions
Audio // Применить к звуку через AudioManager (музыка или эффекты)
}
public enum AudioTargetType
{
Music, // Музыка
Sfx // Звуковые эффекты
}
public enum IntDisplayFormat
{
Slider, // Обычный слайдер
ValueSelector // Выбор значения (со стрелками или дропдауном)
}
[Serializable]
@ -55,6 +55,12 @@ namespace RR.Common.Settings
[TextArea] public string description;
public string group = "General";
[Tooltip("Показывать ли настройку в UI меню настроек")]
public bool shownInSettings = true;
[Tooltip("Доступна ли настройка для изменения извне (через API/файл)")]
public bool exposed = false;
public SettingValueType valueType;
// Defaults
@ -66,19 +72,26 @@ namespace RR.Common.Settings
// 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;
public ApplyTarget applyTarget = ApplyTarget.None;
// AudioMixer params (used only for AudioMixerFloat01ToDb)
public AudioMixer mixer;
public string mixerParameter = "MasterVolumeDb";
public float minDb = -80f;
public float maxDb = 0f;
// Audio params (используется только для ApplyTarget.Audio)
[Tooltip("Тип звука: музыка или звуковые эффекты")]
public AudioTargetType audioTargetType = AudioTargetType.Music;
// Button params (для типа Button)
[Header("Button Settings")]
[Tooltip("Текст на кнопке (если не задан displayName)")]
public string buttonText;
@ -92,7 +105,6 @@ namespace RR.Common.Settings
public Sprite buttonSprite;
// OptionPicker params (для типа OptionPicker)
[Header("Option Picker Settings")]
[Tooltip("Список вариантов выбора")]
public List<string> options;

View File

@ -166,6 +166,36 @@ namespace RR.Common.Settings
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>
@ -173,6 +203,58 @@ namespace RR.Common.Settings
{
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 clean = def.Sanitize(e.value);
map[e.key] = clean;
SettingsApplier.Apply(def, clean);
OnSettingChanged?.Invoke(e.key, clean);
}
Debug.Log($"[SettingsManager] Reloaded settings from {path}");
}
}
catch (Exception ex)
{
Debug.LogError($"[SettingsManager] Failed to reload settings: {ex.Message}");
}
}
public void ApplyAll()
{
@ -219,6 +301,11 @@ namespace RR.Common.Settings
}
private void LoadOrDefaults()
{
LoadOrDefaults(checkExposed: false);
}
private void LoadOrDefaults(bool checkExposed)
{
map.Clear();
@ -231,8 +318,22 @@ namespace RR.Common.Settings
if (data != null)
{
foreach (SettingsSaveData.Entry e in data.entries)
if (!string.IsNullOrWhiteSpace(e.key))
map[e.key] = e.value;
{
if (string.IsNullOrWhiteSpace(e.key)) continue;
// Если checkExposed = true, проверяем флаг exposed
if (checkExposed)
{
SettingEntry def = Define(e.key);
if (def != null && !def.exposed)
{
Debug.LogWarning($"[SettingsManager] Skipping '{e.key}' from file - not exposed.");
continue;
}
}
map[e.key] = e.value;
}
}
}
catch

View File

@ -17,6 +17,8 @@ MonoBehaviour:
displayName: "\u0413\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C \u043C\u0443\u0437\u044B\u043A\u0438"
description:
group: "\u041C\u0443\u0437\u044B\u043A\u0430 \u0438 \u0437\u0432\u0443\u043A\u0438"
shownInSettings: 1
exposed: 0
valueType: 2
defaultBool: 0
defaultInt: 0
@ -24,13 +26,13 @@ MonoBehaviour:
defaultString:
minInt: 0
maxInt: 0
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 1
applyTarget: 1
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: MusicVolume
minDb: -80
maxDb: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
@ -41,6 +43,8 @@ MonoBehaviour:
displayName: "\u0413\u0440\u043E\u043C\u043A\u043E\u0441\u0442\u044C \u0437\u0432\u0443\u043A\u043E\u0432"
description:
group: "\u041C\u0443\u0437\u044B\u043A\u0430 \u0438 \u0437\u0432\u0443\u043A\u0438"
shownInSettings: 1
exposed: 0
valueType: 2
defaultBool: 0
defaultInt: 0
@ -48,13 +52,13 @@ MonoBehaviour:
defaultString:
minInt: 0
maxInt: 0
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 1
applyTarget: 1
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: SFXVolume
minDb: -80
maxDb: 0
audioTargetType: 1
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
@ -65,6 +69,8 @@ MonoBehaviour:
displayName: "\u0411\u0435\u0441\u043F\u043B\u0430\u0442\u043D\u0430\u044F \u0438\u0433\u0440\u0430"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
shownInSettings: 1
exposed: 0
valueType: 0
defaultBool: 0
defaultInt: 0
@ -72,24 +78,80 @@ MonoBehaviour:
defaultString:
minInt: 0
maxInt: 0
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: SFXVolume
minDb: -80
maxDb: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: payment.smallPrize
- key: payment.supportNumber
displayName: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043D\u043E\u043C\u0435\u0440
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
shownInSettings: 1
exposed: 0
valueType: 0
defaultBool: 1
defaultInt: 0
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 0
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: payment.paymentImpulseCost
displayName: "\u0421\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C \u043E\u0434\u043D\u043E\u0433\u043E
\u0438\u043C\u043F\u0443\u043B\u044C\u0441\u0430"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
shownInSettings: 1
exposed: 0
valueType: 1
defaultBool: 0
defaultInt: 10
defaultFloat: 0
defaultString:
minInt: 10
maxInt: 1000
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: prizes.giveSmallPrize
displayName: "\u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0438\u0439
\u043F\u0440\u0438\u0437"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 1
exposed: 0
valueType: 0
defaultBool: 0
defaultInt: 0
@ -97,23 +159,132 @@ MonoBehaviour:
defaultString:
minInt: 0
maxInt: 0
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: SFXVolume
minDb: -80
maxDb: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: prizes.giveBigPrize
displayName: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C QR-\u043A\u043E\u0434\u044B
\u0434\u043B\u044F \u0441\u0432\u044F\u0437\u0438"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 1
exposed: 0
valueType: 0
defaultBool: 1
defaultInt: 1
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
buttonData:
buttonColor: {r: 0.563809, g: 0.8509804, b: 0.13333333, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: prizes.errorGivePrize
displayName: "\u0412\u044B\u0434\u0430\u0432\u0430\u0442\u044C \u043F\u0440\u0438\u0437
\u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 1
exposed: 0
valueType: 0
defaultBool: 0
defaultInt: 1
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
buttonData:
buttonColor: {r: 0.563809, g: 0.8509804, b: 0.13333333, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: prizes.showPrizeError
displayName: "\u0412\u044B\u0432\u043E\u0434 \u043E\u0448\u0438\u0431\u043A\u0438
\u0432\u044B\u0434\u0430\u0447\u0438 \u0432 \u043C\u0435\u043D\u044E"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 1
exposed: 0
valueType: 0
defaultBool: 0
defaultInt: 1
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
buttonData:
buttonColor: {r: 0.563809, g: 0.8509804, b: 0.13333333, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: prizes.phoneNumber
displayName: "\u041D\u043E\u043C\u0435\u0440 \u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0430"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 0
exposed: 0
valueType: 3
defaultBool: 0
defaultInt: 1
defaultFloat: 0
defaultString: +79123456789
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
buttonData:
buttonColor: {r: 0.8490566, g: 0.40749258, b: 0.13216448, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: gameplay.startLives
displayName: "\u041D\u0430\u0447\u0430\u043B\u044C\u043D\u044B\u0435 \u0436\u0438\u0437\u043D\u0438"
description:
group: "\u0418\u0433\u0440\u043E\u0432\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441"
shownInSettings: 1
exposed: 0
valueType: 1
defaultBool: 0
defaultInt: 3
@ -121,13 +292,13 @@ MonoBehaviour:
defaultString:
minInt: 1
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: SFXVolume
minDb: -80
maxDb: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
@ -139,6 +310,8 @@ MonoBehaviour:
\u043C\u043E\u043D\u0435\u0442\u044B"
description:
group: "\u0418\u0433\u0440\u043E\u0432\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441"
shownInSettings: 1
exposed: 0
valueType: 1
defaultBool: 0
defaultInt: 1
@ -146,13 +319,13 @@ MonoBehaviour:
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: SFXVolume
minDb: -80
maxDb: 0
audioTargetType: 0
buttonText:
buttonData:
buttonColor: {r: 0, g: 0, b: 0, a: 0}
@ -162,7 +335,9 @@ MonoBehaviour:
- key: btn.statistics
displayName: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
shownInSettings: 1
exposed: 0
valueType: 4
defaultBool: 0
defaultInt: 1
@ -170,16 +345,98 @@ MonoBehaviour:
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
mixer: {fileID: 24100000, guid: 4f44de6a220ee7d4b8179285074ead2d, type: 2}
mixerParameter: SFXVolume
minDb: -80
maxDb: 0
audioTargetType: 0
buttonText: "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430"
buttonData:
buttonColor: {r: 0.8490566, g: 0.40749258, b: 0.13216448, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: btn.phoneNumber
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
\u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0430"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 1
exposed: 0
valueType: 4
defaultBool: 0
defaultInt: 1
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
\u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0430"
buttonData:
buttonColor: {r: 0.8490566, g: 0.40749258, b: 0.13216448, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: btn.bigPrize
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
description:
group: "\u041F\u0440\u0438\u0437\u044B \u0438 \u0432\u044B\u0434\u0430\u0447\u0430"
shownInSettings: 1
exposed: 0
valueType: 4
defaultBool: 0
defaultInt: 1
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 20
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0443\u043F\u0435\u0440\u043F\u0440\u0438\u0437"
buttonData:
buttonColor: {r: 0.563809, g: 0.8509804, b: 0.13333333, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0
- key: btn.supportNumber
displayName: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
description:
group: "\u041E\u043F\u043B\u0430\u0442\u0430 \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"
shownInSettings: 1
exposed: 0
valueType: 4
defaultBool: 1
defaultInt: 0
defaultFloat: 0
defaultString:
minInt: 0
maxInt: 0
intDisplayFormat: 0
intStep: 1
minFloat: 0
maxFloat: 1
displayAsPercentage: 0
applyTarget: 0
audioTargetType: 0
buttonText: "\u0412\u0432\u0435\u0441\u0442\u0438 \u043D\u043E\u043C\u0435\u0440
\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438"
buttonData:
buttonColor: {r: 0.16300285, g: 0.64997125, b: 0.9339623, a: 1}
buttonSprite: {fileID: 0}
options: []
defaultOptionIndex: 0

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RR.Common.Settings;
using TMPro;
using UnityEngine;
@ -107,40 +106,7 @@ namespace RR.Common.UI
{
if (entry == null) return;
// Спец-случаи с Dropdown
if (entry.applyTarget == ApplyTarget.QualityLevel)
{
UseDropdown(QualitySettings.names.ToList(), onChanged: idx =>
SetSafe(entry.key, SettingValue.Int(idx)));
return;
}
if (entry.applyTarget == ApplyTarget.ResolutionIndex)
{
Resolution[] rs = Screen.resolutions;
List<string> options = rs.Select(t => $"{t.width} x {t.height}").ToList();
UseDropdown(options, onChanged: idx =>
SetSafe(entry.key, SettingValue.Int(idx)));
return;
}
if (entry.applyTarget == ApplyTarget.FullscreenMode)
{
// индексы 0..3 как в твоём SettingsApplier
List<string> options = new()
{
"Exclusive Fullscreen",
"Fullscreen Window",
"Maximized Window",
"Windowed"
};
UseDropdown(options, onChanged: idx =>
SetSafe(entry.key, SettingValue.Int(idx)));
return;
}
// Обычные по типу
// Настройки по valueType
switch (entry.valueType)
{
case SettingValueType.Bool:
@ -343,9 +309,24 @@ namespace RR.Common.UI
{
if (!sliderValueText || !slider || !slider.gameObject.activeSelf) return;
sliderValueText.text = slider.wholeNumbers
? Mathf.RoundToInt(slider.value).ToString()
: slider.value.ToString("0.##");
if (slider.wholeNumbers)
{
sliderValueText.text = Mathf.RoundToInt(slider.value).ToString();
}
else
{
// Проверяем флаг displayAsPercentage
if (entry != null && entry.displayAsPercentage)
{
// Отображаем как проценты (0-100%)
float percentage = slider.value * 100f;
sliderValueText.text = percentage.ToString("0") + "%";
}
else
{
sliderValueText.text = slider.value.ToString("0.##");
}
}
}
}
}

View File

@ -142,7 +142,7 @@ namespace RR.Common.UI
spawnedItems.Clear();
List<SettingEntry> entries = database.entries
.Where(e => e != null && !string.IsNullOrWhiteSpace(e.key))
.Where(e => e != null && !string.IsNullOrWhiteSpace(e.key) && e.shownInSettings)
.ToList();
if (!groupByGroup)
@ -239,24 +239,17 @@ namespace RR.Common.UI
private SettingUIItem PickPrefab(SettingEntry e)
{
return e.applyTarget switch
return e.valueType switch
{
// Спец-UI по ApplyTarget
ApplyTarget.QualityLevel => dropdownRowPrefab,
ApplyTarget.ResolutionIndex => dropdownRowPrefab,
ApplyTarget.FullscreenMode => dropdownRowPrefab,
_ => e.valueType switch
{
// По типу
SettingValueType.Bool => toggleRowPrefab,
SettingValueType.Float => sliderRowPrefab,
SettingValueType.Int => sliderRowPrefab ? sliderRowPrefab : dropdownRowPrefab,
SettingValueType.String => inputRowPrefab,
SettingValueType.Button => buttonRowPrefab,
SettingValueType.OptionPicker => optionPickerRowPrefab ? optionPickerRowPrefab : dropdownRowPrefab,
_ => null
}
// По типу
SettingValueType.Bool => toggleRowPrefab,
SettingValueType.Float => sliderRowPrefab,
SettingValueType.Int => sliderRowPrefab ? sliderRowPrefab : dropdownRowPrefab,
SettingValueType.String => inputRowPrefab,
SettingValueType.Button => buttonRowPrefab,
SettingValueType.OptionPicker => optionPickerRowPrefab ? optionPickerRowPrefab : dropdownRowPrefab,
_ => null
};
}

View File

@ -154,32 +154,30 @@ namespace RR.Common.UI
private Vector2 GetTargetPosition(RectTransform target)
{
// Получаем позицию целевого элемента относительно content
Vector2 targetAnchoredPos = target.anchoredPosition;
// Если элементы не прямые дети content, нужно пересчитать позицию
if (target.parent == content)
return new Vector2(
pointerRect.anchoredPosition.x + offset.x, // X оставляем как есть (или применяем offset)
targetAnchoredPos.y + offset.y // Y берем от целевого элемента
);
// Конвертируем мировую позицию в локальную позицию content
// Получаем мировые координаты центра целевого элемента
Vector3[] corners = new Vector3[4];
target.GetWorldCorners(corners);
Vector3 center = (corners[0] + corners[2]) * 0.5f;
Vector3 worldCenter = (corners[0] + corners[2]) * 0.5f;
// Конвертируем мировую позицию в локальную позицию родителя указателя
RectTransform pointerParent = pointerRect.parent as RectTransform;
if (pointerParent == null)
{
Debug.LogWarning("UIPointer: pointer не имеет RectTransform родителя!");
return pointerRect.anchoredPosition;
}
RectTransformUtility.ScreenPointToLocalPointInRectangle(
content,
RectTransformUtility.WorldToScreenPoint(null, center),
pointerParent,
RectTransformUtility.WorldToScreenPoint(null, worldCenter),
null,
out Vector2 localPos
);
targetAnchoredPos = localPos;
// Применяем смещение
// X фиксирована, меняется только Y
return new Vector2(
pointerRect.anchoredPosition.x + offset.x, // X оставляем как есть (или применяем offset)
targetAnchoredPos.y + offset.y // Y берем от целевого элемента
pointerRect.anchoredPosition.x, // X остается как есть
localPos.y + offset.y // Y от целевого элемента + смещение
);
}