#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(); 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 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 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 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 GetSelectedHierarchyObjects() { return Selection.gameObjects .Where(gameObject => gameObject != null && gameObject.scene.IsValid()) .OrderBy(gameObject => GetHierarchySortKey(gameObject.transform)) .ToList(); } private static string GetHierarchySortKey(Transform target) { List 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