Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save FleshMobProductions/1462f0a50653e981317cc2e3667d506d to your computer and use it in GitHub Desktop.
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
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