using System.Collections.Generic; using UnityEditor; using UnityEditorInternal; using UnityEngine; public class PrefabChildBatchCreatorWindow : EditorWindow { private readonly List _prefabs = new List(); private string _childName = "NewChild"; private bool _replaceIfExists = false; private bool _copyLayerAndTagFromParent = true; private bool _copyParentLocalScale = true; private bool _useCustomLocalPosition = true; private bool _useCustomLocalRotation = true; private Vector3 _childLocalPosition = Vector3.zero; private Vector3 _childLocalEulerRotation = Vector3.zero; private Vector2 _scroll; [MenuItem("Tools/Prefabs/Batch Create Child")] public static void ShowWindow() { var window = GetWindow("Batch Child Creator"); window.minSize = new Vector2(500f, 450f); } private void OnGUI() { EditorGUILayout.LabelField("Batch Create Child For Prefabs", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "Add multiple prefab assets below. The tool creates one child under each prefab root, then moves all root components to the child.", MessageType.Info); DrawPrefabDropArea(); DrawPrefabList(); EditorGUILayout.Space(); EditorGUILayout.LabelField("Child Settings", EditorStyles.boldLabel); _childName = EditorGUILayout.TextField("Child Name", _childName); _replaceIfExists = EditorGUILayout.ToggleLeft("Replace existing child with same name", _replaceIfExists); _copyLayerAndTagFromParent = EditorGUILayout.ToggleLeft("Copy parent layer/tag/static", _copyLayerAndTagFromParent); _copyParentLocalScale = EditorGUILayout.ToggleLeft("Copy parent local scale", _copyParentLocalScale); EditorGUILayout.Space(4f); _useCustomLocalPosition = EditorGUILayout.ToggleLeft("Set custom local position", _useCustomLocalPosition); using (new EditorGUI.DisabledScope(!_useCustomLocalPosition)) { _childLocalPosition = EditorGUILayout.Vector3Field("Local Position", _childLocalPosition); } _useCustomLocalRotation = EditorGUILayout.ToggleLeft("Set custom local rotation", _useCustomLocalRotation); using (new EditorGUI.DisabledScope(!_useCustomLocalRotation)) { _childLocalEulerRotation = EditorGUILayout.Vector3Field("Local Rotation (Euler)", _childLocalEulerRotation); } EditorGUILayout.Space(12f); using (new EditorGUI.DisabledScope(_prefabs.Count == 0 || string.IsNullOrWhiteSpace(_childName))) { if (GUILayout.Button("Create Child For All Prefabs", GUILayout.Height(30f))) { CreateChildrenForPrefabs(); } } } private void DrawPrefabDropArea() { Event evt = Event.current; Rect dropRect = GUILayoutUtility.GetRect(0f, 60f, GUILayout.ExpandWidth(true)); GUI.Box(dropRect, "Drag Prefab Assets Here"); if (!dropRect.Contains(evt.mousePosition)) { return; } if (evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (evt.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); foreach (Object obj in DragAndDrop.objectReferences) { TryAddPrefab(obj as GameObject); } } evt.Use(); } } private void DrawPrefabList() { EditorGUILayout.Space(4f); EditorGUILayout.LabelField($"Prefabs ({_prefabs.Count})", EditorStyles.boldLabel); _scroll = EditorGUILayout.BeginScrollView(_scroll, GUILayout.Height(160f)); int removeIndex = -1; for (int i = 0; i < _prefabs.Count; i++) { EditorGUILayout.BeginHorizontal(); _prefabs[i] = (GameObject)EditorGUILayout.ObjectField(_prefabs[i], typeof(GameObject), false); if (GUILayout.Button("X", GUILayout.Width(28f))) { removeIndex = i; } EditorGUILayout.EndHorizontal(); } if (removeIndex >= 0) { _prefabs.RemoveAt(removeIndex); } EditorGUILayout.EndScrollView(); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Add Selected Prefabs")) { foreach (Object obj in Selection.objects) { TryAddPrefab(obj as GameObject); } } if (GUILayout.Button("Clear List")) { _prefabs.Clear(); } EditorGUILayout.EndHorizontal(); } private void TryAddPrefab(GameObject prefab) { if (prefab == null) { return; } string path = AssetDatabase.GetAssetPath(prefab); if (string.IsNullOrEmpty(path)) { return; } if (PrefabUtility.GetPrefabAssetType(prefab) == PrefabAssetType.NotAPrefab) { return; } if (!_prefabs.Contains(prefab)) { _prefabs.Add(prefab); } } private void CreateChildrenForPrefabs() { int successCount = 0; int failCount = 0; string childName = _childName.Trim(); foreach (GameObject prefab in _prefabs) { if (prefab == null) { failCount++; continue; } string path = AssetDatabase.GetAssetPath(prefab); if (string.IsNullOrEmpty(path)) { failCount++; continue; } GameObject root = PrefabUtility.LoadPrefabContents(path); try { if (root == null) { failCount++; continue; } Transform existing = root.transform.Find(childName); if (existing != null) { if (_replaceIfExists) { DestroyImmediate(existing.gameObject); } else { failCount++; Debug.LogWarning($"Skipped `{prefab.name}` because child `{childName}` already exists."); continue; } } GameObject child = new GameObject(childName); child.transform.SetParent(root.transform, false); if (_copyLayerAndTagFromParent) { child.layer = root.layer; child.tag = root.tag; GameObjectUtility.SetStaticEditorFlags( child, GameObjectUtility.GetStaticEditorFlags(root)); } if (_copyParentLocalScale) { child.transform.localScale = root.transform.localScale; } if (_useCustomLocalPosition) { child.transform.localPosition = _childLocalPosition; } if (_useCustomLocalRotation) { child.transform.localRotation = Quaternion.Euler(_childLocalEulerRotation); } MoveAllComponentsFromParentToChild(root, child, prefab.name); PrefabUtility.SaveAsPrefabAsset(root, path); successCount++; } catch (System.Exception ex) { failCount++; Debug.LogError($"Failed processing `{prefab.name}` at path `{path}`. Error: {ex.Message}"); } finally { PrefabUtility.UnloadPrefabContents(root); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); EditorUtility.DisplayDialog( "Batch Child Creator", $"Done.\nSuccess: {successCount}\nFailed/Skipped: {failCount}", "OK"); } private static void MoveAllComponentsFromParentToChild(GameObject parent, GameObject child, string prefabName) { var componentsToRemove = new List(); Component[] parentComponents = parent.GetComponents(); foreach (Component component in parentComponents) { if (component == null || component is Transform) { continue; } bool copied = ComponentUtility.CopyComponent(component); if (!copied) { Debug.LogWarning($"Could not copy component `{component.GetType().Name}` in prefab `{prefabName}`."); continue; } bool pasted = ComponentUtility.PasteComponentAsNew(child); if (!pasted) { Debug.LogWarning($"Could not paste component `{component.GetType().Name}` into child in prefab `{prefabName}`."); continue; } componentsToRemove.Add(component); } foreach (Component component in componentsToRemove) { if (component != null) { Object.DestroyImmediate(component, true); } } } }