Danro/Assets/Scripts/Editor/SettingEntryDrawer.cs
Danil Iarosh cb2e83a596
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat(ui, prizes, spawners, editor): expand prize flow and add spawning/editor tooling
- доработан flow призовой витрины и связанного UI:
  добавлены `PrizeStandButtonView` и `RulesPrizeListPage`, обновлены
  `PrizeStandController`, `PrizeStandPrize`, `PrizeStandModal`,
  `PrizeStandSettingsModal`, `LevelPrizeSettingsModal`, `QRCodeSettingsModal`,
  `BalanceDisplay`, `ButtonTextFocusColor` и связанные prefab'ы,
  чтобы улучшить настройку, навигацию, отображение кнопок и prize flow
- расширена система настроек и сохранения значений:
  `OptionPicker` получил отдельный формат `ValueSelector`,
  обновлены `SettingsDatabase`, `SettingEntryDrawer`, `SettingUIItem`,
  `SettingsUIGenerator`, `ValueSelectorRow` и `MainSettingsDatabase.asset`,
  а сохранение option picker-значений, включая `gameplay.gameDifficulty`,
  исправлено и синхронизировано с остальными настройками
- добавлены универсальные спавнеры сущностей:
  реализованы `EntitySpawner` и `EntitySpawnerManager` с поддержкой
  весов префабов, точек спавна, заполнения по проценту, ограничений
  на количество и автоспавна при инициализации
- доработана логика игрового спавна и сложности:
  процент инициализационного спавна может зависеть от настройки
  `gameplay.gameDifficulty` с градациями 25/50/75/100,
  в одном цикле массового спавна повторно не используется та же точка,
  а gizmo спавнера теперь показывает только сами точки без линий
- упрощена работа в редакторе Unity и добавлены editor-инструменты:
  в `GameManager` лицензия пропускается при запуске в `UNITY_EDITOR`,
  а `HierarchySelectionCounter`, `HierarchySelectionCountOverlay`
  и `HierarchyBulkRenamerWindow` добавляют подсчет выделения,
  realtime overlay в `SceneView` и массовое переименование объектов
  по правилам нумерации из Unity Project Settings
- синхронизированы сцены, ресурсы и UI-ассеты под новый flow:
  обновлены `MainMenu`, `Level UI`, уровни `1-1` и `1-2`,
  строки настроек, кнопки, TMP/font assets, изображения UI,
  `prizes.json` и recovery-данные после правок интерфейса и логики
- очищены и переразложены часть внешних и служебных ресурсов:
  удалён пакет `Lava_Flowing_Shader`, переименованы/заменены
  отдельные UI-prefab'ы и добавлены сопутствующие проектные артефакты,
  которые сейчас используются обновлённой конфигурацией проекта
2026-04-28 14:05:05 +03:00

362 lines
16 KiB
C#

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using RR.Common.Settings;
namespace RR.Common.InspectorOverrides
{
/// <summary>
/// Custom property drawer for SettingEntry - v1.2
/// </summary>
[CustomPropertyDrawer(typeof(SettingEntry))]
public class SettingEntryDrawer : PropertyDrawer
{
private const float Pad = 6f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Чтоб выглядело стабильнее в узких инспекторах
bool prevWide = EditorGUIUtility.wideMode;
float prevLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.wideMode = true;
EditorGUIUtility.labelWidth = 110f;
EditorGUI.BeginProperty(position, label, property);
var keyProp = property.FindPropertyRelative("key");
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");
var defIntProp = property.FindPropertyRelative("defaultInt");
var defFloatProp = property.FindPropertyRelative("defaultFloat");
var defStringProp = property.FindPropertyRelative("defaultString");
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 floatDisplayFormatProp = property.FindPropertyRelative("floatDisplayFormat");
var floatStepProp = property.FindPropertyRelative("floatStep");
var optionPickerDisplayFormatProp = property.FindPropertyRelative("optionPickerDisplayFormat");
var applyProp = property.FindPropertyRelative("applyTarget");
var audioTargetProp = property.FindPropertyRelative("audioTargetType");
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
// Card
var box = new Rect(position.x, position.y, position.width, position.height);
GUI.Box(box, GUIContent.none, EditorStyles.helpBox);
var r = new Rect(position.x + Pad, position.y + Pad, position.width - Pad * 2, line);
// Header foldout
string header = BuildHeader(nameProp.stringValue, keyProp.stringValue);
property.isExpanded = EditorGUI.Foldout(r, property.isExpanded, header, true);
r.y += line + space;
if (!property.isExpanded)
{
EditorGUI.EndProperty();
EditorGUIUtility.wideMode = prevWide;
EditorGUIUtility.labelWidth = prevLabelWidth;
return;
}
DrawSectionTitle(ref r, "Meta");
DrawProp(ref r, nameProp, "Display Name");
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");
var t = (SettingValueType)typeProp.enumValueIndex;
switch (t)
{
case SettingValueType.Bool:
DrawProp(ref r, defBoolProp, "Default");
break;
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 %");
DrawProp(ref r, floatDisplayFormatProp, "Display Format");
// Показываем floatStep только если выбран ValueSelector
var floatDisplayFormat = (FloatDisplayFormat)floatDisplayFormatProp.enumValueIndex;
if (floatDisplayFormat == FloatDisplayFormat.ValueSelector)
{
DrawProp(ref r, floatStepProp, "Step");
}
break;
case SettingValueType.String:
DrawProp(ref r, defStringProp, "Default");
break;
case SettingValueType.Button:
DrawSectionTitle(ref r, "Button Settings");
var buttonTextProp = property.FindPropertyRelative("buttonText");
var buttonDataProp = property.FindPropertyRelative("buttonData");
var buttonColorProp = property.FindPropertyRelative("buttonColor");
var buttonSpriteProp = property.FindPropertyRelative("buttonSprite");
DrawProp(ref r, buttonTextProp, "Button Text");
DrawProp(ref r, buttonDataProp, "Button Data");
DrawProp(ref r, buttonColorProp, "Button Color");
DrawProp(ref r, buttonSpriteProp, "Button Sprite");
break;
case SettingValueType.OptionPicker:
DrawSectionTitle(ref r, "Option Picker Settings");
var optionsProp = property.FindPropertyRelative("options");
var defOptionIndexProp = property.FindPropertyRelative("defaultOptionIndex");
DrawProp(ref r, optionsProp, "Options", includeChildren: true);
DrawProp(ref r, defOptionIndexProp, "Default Index");
DrawProp(ref r, optionPickerDisplayFormatProp, "Display Format");
break;
}
DrawSectionTitle(ref r, "Apply");
DrawProp(ref r, applyProp, "Apply Target");
var apply = (ApplyTarget)applyProp.enumValueIndex;
if (apply == ApplyTarget.Audio)
{
DrawProp(ref r, audioTargetProp, "Audio Type");
}
// Warning
if (string.IsNullOrWhiteSpace(keyProp.stringValue))
{
var warnH = line * 1.4f;
var warn = new Rect(r.x, r.y, r.width, warnH);
EditorGUI.HelpBox(warn, "Key is empty - setting won't work.", MessageType.Warning);
r.y += warnH + space;
}
EditorGUI.EndProperty();
// restore
EditorGUIUtility.wideMode = prevWide;
EditorGUIUtility.labelWidth = prevLabelWidth;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
float h = 0f;
// padding + foldout
h += Pad * 2;
h += line + space;
if (!property.isExpanded)
return h + 2f;
SerializedProperty keyProp = property.FindPropertyRelative("key");
SerializedProperty nameProp = property.FindPropertyRelative("displayName");
SerializedProperty groupProp = property.FindPropertyRelative("group");
SerializedProperty descProp = property.FindPropertyRelative("description");
SerializedProperty typeProp = property.FindPropertyRelative("valueType");
SerializedProperty applyProp = property.FindPropertyRelative("applyTarget");
// Meta section title
h += line + space;
h += HeightOf(nameProp) + space;
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;
h += HeightOf(typeProp) + space;
// Default + optional range row
SettingValueType t = (SettingValueType)typeProp.enumValueIndex;
switch (t)
{
case SettingValueType.Bool:
case SettingValueType.String:
h += line + space; // Default
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
h += line + space; // floatDisplayFormat
// Добавляем высоту для floatStep если выбран ValueSelector
var floatDisplayFormatProp = property.FindPropertyRelative("floatDisplayFormat");
if (floatDisplayFormatProp != null)
{
var fDisplayFormat = (FloatDisplayFormat)floatDisplayFormatProp.enumValueIndex;
if (fDisplayFormat == FloatDisplayFormat.ValueSelector)
{
h += line + space; // floatStep
}
}
break;
case SettingValueType.Button:
h += line + space; // section title
h += line + space; // buttonText
h += line + space; // buttonData
h += line + space; // buttonColor
h += line + space; // buttonSprite
break;
case SettingValueType.OptionPicker:
h += line + space; // section title
var optionsProp = property.FindPropertyRelative("options");
h += HeightOf(optionsProp, true) + space; // options list
h += line + space; // defaultOptionIndex
h += line + space; // optionPickerDisplayFormat
break;
}
// Apply section title
h += line + space;
h += HeightOf(applyProp) + space;
ApplyTarget apply = (ApplyTarget)applyProp.enumValueIndex;
if (apply == ApplyTarget.Audio)
{
h += line + space; // audioTargetType
}
if (string.IsNullOrWhiteSpace(keyProp.stringValue))
h += line * 1.4f + space;
return h + 6f;
}
private static float HeightOf(SerializedProperty prop, bool includeChildren = false)
=> EditorGUI.GetPropertyHeight(prop, includeChildren);
private static void DrawProp(ref Rect r, SerializedProperty prop, string label, bool includeChildren = false)
{
float space = EditorGUIUtility.standardVerticalSpacing;
GUIContent gc = new(label);
float h = EditorGUI.GetPropertyHeight(prop, gc, includeChildren);
Rect rect = new(r.x, r.y, r.width, h);
EditorGUI.PropertyField(rect, prop, gc, includeChildren);
r.y += h + space;
}
private static void DrawSectionTitle(ref Rect r, string title)
{
float line = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
Rect rect = new(r.x, r.y, r.width, line);
EditorGUI.LabelField(rect, title, EditorStyles.boldLabel);
r.y += line + space;
}
private static void DrawMinMaxRow(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);
EditorGUI.PropertyField(left, leftProp, new GUIContent(leftLabel));
EditorGUI.PropertyField(right, rightProp, new GUIContent(rightLabel));
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)
{
if (!string.IsNullOrWhiteSpace(displayName) && !string.IsNullOrWhiteSpace(key))
return $"{displayName} ({key})";
if (!string.IsNullOrWhiteSpace(displayName)) return displayName;
return !string.IsNullOrWhiteSpace(key) ? key : "Setting";
}
}
}
#endif