using System.Linq; using UnityEngine; using UnityEditor; // This code has been adapted from a tutorial by Patryk Galach // https://www.patrykgalach.com/2019/08/26/replace-tool-for-level-designers /// /// Replace tool window,available under the Tools menu. /// public class ReplaceTool : EditorWindow { ReplaceData data; SerializedObject serializedData; // Prefab variable from data object. Using SerializedProperty for integrated Undo SerializedProperty replaceObjectField; // Scroll position for list of selected objects Vector2 selectObjectScrollPosition; private void InitDataIfNeeded() { if (!data) { data = ScriptableObject.CreateInstance(); serializedData = null; } // If data was not wrapped into SerializedObject, wrap it if (serializedData == null) { serializedData = new SerializedObject(data); replaceObjectField = null; } // If prefab field was not assigned as SerializedProperty, assign it if (replaceObjectField == null) { replaceObjectField = serializedData.FindProperty("replacementPrefab"); } } // Register menu item to open Window [MenuItem("Tools/Replace with Prefab")] public static void ShowWindow() { var window = GetWindow(); window.Show(); } private void OnGUI() { InitDataIfNeeded(); EditorGUILayout.Separator(); EditorGUILayout.PropertyField(replaceObjectField); EditorGUILayout.Separator(); EditorGUILayout.LabelField("Selected objects to replace", EditorStyles.boldLabel); EditorGUILayout.Separator(); // Saving number of objects to replace. int objectToReplaceCount = data.objectsToReplace != null ? data.objectsToReplace.Length : 0; EditorGUILayout.IntField("Object count", objectToReplaceCount); EditorGUI.indentLevel++; // Printing information when no object is selected on scene. if (objectToReplaceCount == 0) { EditorGUILayout.Separator(); EditorGUILayout.LabelField("Select objects in the hierarchy to replace them", EditorStyles.wordWrappedLabel); } // Read-only scroll view with selected game objects. selectObjectScrollPosition = EditorGUILayout.BeginScrollView(selectObjectScrollPosition); GUI.enabled = false; if (data && data.objectsToReplace != null) { foreach (var go in data.objectsToReplace) { EditorGUILayout.ObjectField(go, typeof(GameObject), true); } } GUI.enabled = true; EditorGUILayout.EndScrollView(); EditorGUI.indentLevel--; EditorGUILayout.Separator(); if (GUILayout.Button("Replace")) { // Check if replace object is assigned. if (!replaceObjectField.objectReferenceValue) { Debug.LogErrorFormat("{0}", "No prefab to replace with!"); return; } // Check if there are objects to replace. if (data.objectsToReplace.Length == 0) { Debug.LogErrorFormat("{0}", "No objects to replace!"); return; } ReplaceSelectedObjects(data.objectsToReplace, data.replacementPrefab); } EditorGUILayout.Separator(); serializedData.ApplyModifiedProperties(); } private void OnInspectorUpdate() { if (serializedData != null && serializedData.UpdateIfRequiredOrScript()) { this.Repaint(); } } private void OnSelectionChange() { InitDataIfNeeded(); SelectionMode objectFilter = SelectionMode.Unfiltered ^ ~(SelectionMode.Assets | SelectionMode.DeepAssets | SelectionMode.Deep); Transform[] selection = Selection.GetTransforms(objectFilter); data.objectsToReplace = selection.Select(s => s.gameObject).ToArray(); if (serializedData.UpdateIfRequiredOrScript()) { this.Repaint(); } } /// /// Replaces game objects with provided replace object. /// /// Game Objects to replace. /// Prefab that will be instantiated in place of the objects to replace. internal static void ReplaceSelectedObjects(GameObject[] objectToReplace, GameObject replaceObject) { var newInstances = new int[objectToReplace.Length]; for (int i = 0; i < objectToReplace.Length; i++) { var go = objectToReplace[i]; Undo.RegisterCompleteObjectUndo(go, "Saving game object state"); var sibling = go.transform.GetSiblingIndex(); var inst = (GameObject)PrefabUtility.InstantiatePrefab(replaceObject); newInstances[i] = inst.GetInstanceID(); inst.transform.position = go.transform.position; inst.transform.rotation = go.transform.rotation; inst.transform.parent = go.transform.parent; inst.transform.localScale = go.transform.localScale; inst.transform.SetSiblingIndex(sibling); Undo.RegisterCreatedObjectUndo(inst, "Replacement creation."); foreach (Transform child in go.transform) { Undo.SetTransformParent(child, inst.transform, "Parent Change"); } Undo.DestroyObjectImmediate(go); } Selection.instanceIDs = newInstances; } } /// /// Data class for replace tool. /// public class ReplaceData : ScriptableObject { public GameObject replacementPrefab; public GameObject[] objectsToReplace; }