Files
test/Assets/Editor/PrefabChildBatchCreatorWindow.cs
2026-04-21 14:43:26 +07:00

294 lines
9.3 KiB
C#

using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
public class PrefabChildBatchCreatorWindow : EditorWindow
{
private readonly List<GameObject> _prefabs = new List<GameObject>();
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<PrefabChildBatchCreatorWindow>("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>();
Component[] parentComponents = parent.GetComponents<Component>();
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);
}
}
}
}