294 lines
9.3 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|