Danro/Assets/Scripts/Editor/HierarchyBulkRenamerWindow.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

251 lines
8.9 KiB
C#

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace RR.Common.InspectorOverrides
{
public sealed class HierarchyBulkRenamerWindow : EditorWindow
{
private enum NamingScheme
{
SpaceParenthesis,
Dot,
Underscore
}
private struct NamingSettings
{
public NamingScheme Scheme;
public int Digits;
public bool StartsAtZero;
}
private string _baseName = "GameObject";
private Vector2 _scrollPosition;
[MenuItem("Tools/Hierarchy/Bulk Rename Selected")]
private static void OpenWindow()
{
HierarchyBulkRenamerWindow window = GetWindow<HierarchyBulkRenamerWindow>();
window.titleContent = new GUIContent("Bulk Rename");
window.minSize = new Vector2(360f, 260f);
window.Show();
}
private void OnEnable()
{
Selection.selectionChanged += Repaint;
}
private void OnDisable()
{
Selection.selectionChanged -= Repaint;
}
private void OnGUI()
{
List<GameObject> selectedObjects = GetSelectedHierarchyObjects();
NamingSettings namingSettings = ReadProjectNamingSettings();
EditorGUILayout.LabelField("Mass Rename Hierarchy Objects", EditorStyles.boldLabel);
EditorGUILayout.Space(4f);
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
EditorGUILayout.LabelField("Base Name", EditorStyles.miniBoldLabel);
_baseName = EditorGUILayout.TextField(_baseName);
}
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
EditorGUILayout.LabelField("Project Numbering Settings", EditorStyles.miniBoldLabel);
EditorGUILayout.LabelField("Scheme", NamingSchemeToText(namingSettings.Scheme));
EditorGUILayout.LabelField("Digits", namingSettings.Digits.ToString());
EditorGUILayout.LabelField("Starts At", namingSettings.StartsAtZero ? "0" : "1");
}
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
EditorGUILayout.LabelField("Selection", EditorStyles.miniBoldLabel);
EditorGUILayout.LabelField($"Selected objects: {selectedObjects.Count}");
string previewBaseName = string.IsNullOrWhiteSpace(_baseName) ? "GameObject" : _baseName.Trim();
string previewName = BuildName(previewBaseName, namingSettings, 0);
EditorGUILayout.LabelField("First preview", previewName);
}
EditorGUILayout.Space(4f);
DrawSelectionPreview(selectedObjects, namingSettings);
using (new EditorGUI.DisabledScope(selectedObjects.Count == 0))
{
if (GUILayout.Button("Rename Selected"))
RenameSelected(selectedObjects, namingSettings);
}
}
private void DrawSelectionPreview(List<GameObject> selectedObjects, NamingSettings namingSettings)
{
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.MinHeight(120f));
if (selectedObjects.Count == 0)
{
EditorGUILayout.LabelField("No hierarchy objects selected.");
}
else
{
string normalizedBaseName = string.IsNullOrWhiteSpace(_baseName) ? "GameObject" : _baseName.Trim();
for (int i = 0; i < selectedObjects.Count; i++)
{
string renamed = BuildName(normalizedBaseName, namingSettings, i);
EditorGUILayout.LabelField($"{selectedObjects[i].name} -> {renamed}");
}
}
EditorGUILayout.EndScrollView();
}
}
private void RenameSelected(List<GameObject> selectedObjects, NamingSettings namingSettings)
{
if (selectedObjects.Count == 0)
return;
string normalizedBaseName = string.IsNullOrWhiteSpace(_baseName) ? "GameObject" : _baseName.Trim();
Undo.RecordObjects(selectedObjects.ToArray(), "Bulk Rename Hierarchy Objects");
for (int i = 0; i < selectedObjects.Count; i++)
{
GameObject gameObject = selectedObjects[i];
if (gameObject == null)
continue;
gameObject.name = BuildName(normalizedBaseName, namingSettings, i);
if (gameObject.scene.IsValid())
EditorSceneManager.MarkSceneDirty(gameObject.scene);
}
EditorUtility.DisplayDialog(
"Bulk Rename",
$"Переименовано объектов: {selectedObjects.Count}",
"OK");
}
private static List<GameObject> GetSelectedHierarchyObjects()
{
return Selection.gameObjects
.Where(gameObject => gameObject != null && gameObject.scene.IsValid())
.OrderBy(gameObject => GetHierarchySortKey(gameObject.transform))
.ToList();
}
private static string GetHierarchySortKey(Transform target)
{
List<int> indices = new();
Transform current = target;
while (current != null)
{
indices.Add(current.GetSiblingIndex());
current = current.parent;
}
indices.Reverse();
return string.Join("/", indices.Select(index => index.ToString("D6")));
}
private static NamingSettings ReadProjectNamingSettings()
{
NamingSettings settings = new NamingSettings
{
Scheme = NamingScheme.SpaceParenthesis,
Digits = 1,
StartsAtZero = false
};
Type editorSettingsType = typeof(EditorSettings);
PropertyInfo schemeProperty = editorSettingsType.GetProperty(
"gameObjectNamingScheme",
BindingFlags.Public | BindingFlags.Static);
if (schemeProperty != null)
{
object value = schemeProperty.GetValue(null);
if (value != null)
settings.Scheme = ParseNamingScheme(value.ToString());
}
PropertyInfo digitsProperty = editorSettingsType.GetProperty(
"gameObjectNamingDigits",
BindingFlags.Public | BindingFlags.Static);
if (digitsProperty != null)
{
object value = digitsProperty.GetValue(null);
if (value is int digits)
settings.Digits = Mathf.Max(1, digits);
}
PropertyInfo startsAtZeroProperty = editorSettingsType.GetProperty(
"gameObjectNamingStartsAtZero",
BindingFlags.Public | BindingFlags.Static);
if (startsAtZeroProperty != null)
{
object value = startsAtZeroProperty.GetValue(null);
if (value is bool startsAtZero)
settings.StartsAtZero = startsAtZero;
}
return settings;
}
private static NamingScheme ParseNamingScheme(string rawValue)
{
if (string.IsNullOrEmpty(rawValue))
return NamingScheme.SpaceParenthesis;
if (rawValue.IndexOf("dot", StringComparison.OrdinalIgnoreCase) >= 0)
return NamingScheme.Dot;
if (rawValue.IndexOf("underscore", StringComparison.OrdinalIgnoreCase) >= 0)
return NamingScheme.Underscore;
return NamingScheme.SpaceParenthesis;
}
private static string BuildName(string baseName, NamingSettings namingSettings, int sequenceIndex)
{
int displayIndex = namingSettings.StartsAtZero ? sequenceIndex : sequenceIndex + 1;
string number = displayIndex.ToString($"D{Mathf.Max(1, namingSettings.Digits)}");
return namingSettings.Scheme switch
{
NamingScheme.Dot => $"{baseName}.{number}",
NamingScheme.Underscore => $"{baseName}_{number}",
_ => $"{baseName} ({number})"
};
}
private static string NamingSchemeToText(NamingScheme namingScheme)
{
return namingScheme switch
{
NamingScheme.Dot => "Dot",
NamingScheme.Underscore => "Underscore",
_ => "Space Parenthesis"
};
}
}
}
#endif