Created
July 14, 2021 15:41
-
-
Save FleshMobProductions/1462f0a50653e981317cc2e3667d506d to your computer and use it in GitHub Desktop.
Replace Scene Objects With Prefab (Unity Editor Tool) - Define filter criteria for objects to select, such as what attached component types they need to have or which tag they need to have, then replace them with a prefab instance of choice
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using UnityEngine.SceneManagement; | |
using System.Linq; | |
namespace FMPUtils.Editor | |
{ | |
public class ReplaceSceneObjectsWithPrefabWindow : EditorWindow | |
{ | |
private GameObject prefab; | |
private bool includeTag = false; | |
private string requiredTag = ""; | |
private string requiredComponentTypesTxt = ""; | |
private List<GameObject> sceneObjects = new List<GameObject>(); | |
private List<GameObject> objectsToReplace = new List<GameObject>(); | |
[MenuItem("FMPUtils/GameObjects/Replace Scene Objects With Prefab")] | |
private static void ShowWindow() | |
{ | |
var window = EditorWindow.GetWindow<ReplaceSceneObjectsWithPrefabWindow>(); | |
window.Show(); | |
} | |
private void OnGUI() | |
{ | |
EditorGUILayout.LabelField("Replace Objects In Active Scene", EditorStyles.boldLabel); | |
EditorGUILayout.Space(); | |
EditorGUILayout.Space(); | |
prefab = (GameObject)EditorGUILayout.ObjectField("Prefab:", prefab, typeof(GameObject), false); | |
EditorGUILayout.Space(); | |
includeTag = EditorGUILayout.BeginToggleGroup("Include Tag in Search?", includeTag); | |
requiredTag = EditorGUILayout.TextField("Required Tag:", requiredTag); | |
EditorGUILayout.EndToggleGroup(); | |
EditorGUILayout.Space(); | |
EditorGUILayout.LabelField("Required Components on Object (Separated by Line Breaks)"); | |
requiredComponentTypesTxt = EditorGUILayout.TextArea(requiredComponentTypesTxt, GUILayout.Height(110)); | |
EditorGUILayout.Space(); | |
if (GUILayout.Button("Replace Scene Objects")) | |
{ | |
ReplaceSceneObjects(); | |
} | |
} | |
// Only replaces the first instance to the topmost of the hierarchy that it can find: | |
private void ReplaceSceneObjects() | |
{ | |
if (!EditorUtility.DisplayDialog("Confirm Replacement?", "Are you sure you want to replace found objets with the selected prefab?", "Yes", "No")) | |
return; | |
if (prefab == null) | |
{ | |
EditorUtility.DisplayDialog("Prefab not assigned", "Assign a valid prefab before continuing", "Okay"); | |
return; | |
} | |
if (string.IsNullOrWhiteSpace(requiredComponentTypesTxt)) | |
{ | |
EditorUtility.DisplayDialog("Insufficient arguments", "Enter a few component names for the required components, separated by an empty line", "Okay"); | |
return; | |
} | |
List<string> requiredComponentTypes = requiredComponentTypesTxt.Split('\n').Select(line => line.Trim()).Where(line => !string.IsNullOrWhiteSpace(line)).ToList(); | |
if (requiredComponentTypes.Count == 0) | |
{ | |
EditorUtility.DisplayDialog("Insufficient arguments", "No Component type strings could be made from the required components text", "Okay"); | |
return; | |
} | |
Scene scene = SceneManager.GetActiveScene(); | |
// Documents say to make sure that the list capacity can hold the rootCount | |
if (sceneObjects.Capacity < scene.rootCount) | |
sceneObjects.Capacity = scene.rootCount; | |
scene.GetRootGameObjects(sceneObjects); | |
objectsToReplace.Clear(); | |
int replacedObjectCounter = 0; | |
foreach (var obj in sceneObjects) | |
{ | |
AttemptObjectSwap(obj.transform, requiredComponentTypes, ref replacedObjectCounter); | |
} | |
EditorUtility.DisplayDialog("Operation finished!", $"{replacedObjectCounter} objects were replaced with the selected prefab", "Okay"); | |
} | |
// Stops at the first object hierarchy layer of detection | |
private void AttemptObjectSwap(Transform current, List<string> requiredComponentTypes, ref int replacedObjectCounter) | |
{ | |
if (current == null) return; | |
if (IsObjectAMatch(current, requiredComponentTypes)) | |
{ | |
ReplaceObjectWithPrefab(current); | |
replacedObjectCounter++; | |
return; | |
} | |
Transform[] children = new Transform[current.childCount]; | |
for (int childId = 0; childId < current.childCount; childId++) | |
{ | |
// Not sure how GetChild behaviour is if we just change the hierarchy during GetChild iteration, better to cache all children: | |
children[childId] = current.GetChild(childId); | |
} | |
for (int childId = 0; childId < children.Length; childId++) | |
{ | |
Transform child = children[childId]; | |
AttemptObjectSwap(child, requiredComponentTypes, ref replacedObjectCounter); | |
} | |
} | |
private void ReplaceObjectWithPrefab(Transform t) | |
{ | |
Transform originalParent = t.parent; | |
GameObject prefabInstance = GameObject.Instantiate(prefab, t.position, t.rotation, originalParent); | |
// Register created object so that it is removed on back stepping | |
Undo.RegisterCreatedObjectUndo(prefabInstance, "Create prefab instance"); | |
// Destroy object but register the destruction so it can be undone by back stepping | |
Undo.DestroyObjectImmediate(t.gameObject); | |
} | |
private bool IsObjectAMatch(Transform t, List<string> requiredComponentTypes) | |
{ | |
if (t == null) | |
return false; | |
if (includeTag && !t.CompareTag(requiredTag)) | |
return false; | |
foreach (string componentType in requiredComponentTypes) | |
{ | |
if (t.GetComponent(componentType) == null) | |
return false; | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment