Skip to content

Instantly share code, notes, and snippets.

@h-sigma
Last active June 7, 2020 18:16
Show Gist options
  • Save h-sigma/461bd8e4645dd02ea5975ce38172d705 to your computer and use it in GitHub Desktop.
Save h-sigma/461bd8e4645dd02ea5975ce38172d705 to your computer and use it in GitHub Desktop.
A
using System.Linq;
using UnityEditor;
namespace Editor.EditorScripts
{
public static class MenuItemsCollection
{
[MenuItem("GameObject/Replace", priority = 21)]
public static void ReplaceObjectInHierarchy(MenuCommand menuCommand)
{
//workaround so menu item executes only once per selected object
if (Selection.objects.Length > 1)
{
//if menu item isn't executing for last object in selection, continue
if (menuCommand.context != Selection.objects[Selection.objects.Length - 1])
return;
}
//Get all selected transforms that are bound to scene view. Take only distinct items (just to be sure).
var selection = Selection.GetTransforms(SelectionMode.ExcludePrefab).Distinct().ToList();
//create editor window for further handling
var window = ReplaceSceneObjects.CreateWindow();
//add all selections to the window object to be replaced later
foreach (var s in selection)
{
window.AddItemToReplace(s.gameObject);
}
}
}
}
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Editor.EditorScripts
{
/// <summary>
/// Simple editor window that allows you to replace scene objects with prefabs while preserving transform properties and place in hierarchy.
/// </summary>
public class ReplaceSceneObjects : EditorWindow
{
/// <summary>
/// Gets the window (including title and utility set to true). Does nothing fancy.
/// </summary>
public static ReplaceSceneObjects CreateWindow()
{
return GetWindow<ReplaceSceneObjects>(true, "Replace Scene Objects", true);
}
/// <summary>
/// List of items to be replaced.
/// </summary>
private List<Item> replaceList;
/// <summary>
/// For use by MenuItemsCollection.
/// </summary>
public void AddItemToReplace(GameObject obj)
{
//sanity checks:
if (replaceList == null) replaceList = new List<Item>(); //1. null list
if (obj == null) return; //2. null object
if (EditorUtility.IsPersistent(obj)) return; //3. persistent object (non-scene)
if (replaceList.Select(item => item.replace).Contains(obj)) return; //4. already added to list
//create new pair and add to list
replaceList.Add(new Item() {replace = obj, opSelected = true});
}
//for use by Object Picker
/// <summary>
/// This object will be set to all selected items' ".with" field. See: Pair
/// </summary>
private GameObject groupReplacementObject;
private bool pickedObjectChanged = false;
/// <summary>
/// Is the object picker being displayed being used for group operation? If false, it was opened by a single ObjectField for individual use.
/// </summary>
private bool isPickerForGroupOperation = false;
public void OnGUI()
{
if (replaceList == null) replaceList = new List<Item>(); //check
#region Object Picker
//https://docs.unity3d.com/ScriptReference/EditorGUIUtility.ShowObjectPicker.html
if (Event.current.type == EventType.ExecuteCommand && isPickerForGroupOperation)
{
if (Event.current.commandName == "ObjectSelectorUpdated")
{
groupReplacementObject =
EditorGUIUtility.GetObjectPickerObject() as GameObject ??
groupReplacementObject; //this should never happen, but sanity check...
pickedObjectChanged = true;
}
else if (Event.current.commandName == "ObjectSelectorClosed")
{
//when object picker is closed, apply the selected item to everything
if (pickedObjectChanged) //only apply change if we changed selection at least once
{
foreach (var item in replaceList.Where(item => item.opSelected))
{
item.with = groupReplacementObject;
}
}
pickedObjectChanged = false;
isPickerForGroupOperation = false;
}
return;
}
EditorGUILayout.BeginHorizontal("Box");
if (GUILayout.Button("Set 'With' for selected items."))
{
EditorGUIUtility.ShowObjectPicker<GameObject>(groupReplacementObject, false, "", 42);
isPickerForGroupOperation = true;
pickedObjectChanged = false;
}
EditorGUILayout.EndHorizontal();
#endregion
#region Display Replacement List
EditorGUILayout.BeginHorizontal(); //COLUMNS
//Replace Column
EditorGUILayout.BeginVertical("Box"); //REPLACE
EditorGUILayout.LabelField("Replace", EditorStyles.boldLabel);
GUI.enabled = false;
for (int i = 0; i < replaceList.Count; i++)
{
EditorGUILayout.ObjectField("", replaceList[i].replace, typeof(GameObject), true);
}
GUI.enabled = true;
EditorGUILayout.EndVertical(); //REPLACE
EditorGUILayout.BeginVertical("Box"); //WITH
EditorGUILayout.LabelField("With", EditorStyles.boldLabel);
for (int i = 0; i < replaceList.Count; i++)
{
replaceList[i].with =
EditorGUILayout.ObjectField("", replaceList[i].with, typeof(GameObject), false) as GameObject;
}
EditorGUILayout.EndVertical(); //WITH
EditorGUILayout.BeginVertical("Box"); //CHECKBOX
EditorGUILayout.LabelField("Change Group", EditorStyles.boldLabel);
for (int i = 0; i < replaceList.Count; i++)
{
replaceList[i].opSelected = EditorGUILayout.Toggle("", replaceList[i].opSelected);
}
EditorGUILayout.EndVertical(); //CHECKBOX
EditorGUILayout.EndHorizontal(); //COLUMNS
#endregion
#region Button Options //probably should size these at some point...
EditorGUILayout.BeginHorizontal("Box"); //SELECTION
if (GUILayout.Button("Select All")) //select all items for operations
{
replaceList.ForEach(item => item.opSelected = true);
}
if (GUILayout.Button("Select None")) //un-select all items for operations
{
replaceList.ForEach(item => item.opSelected = false);
}
if (GUILayout.Button("Invert Selection")) //invert selection
{
replaceList.ForEach(item => item.opSelected = !item.opSelected);
}
EditorGUILayout.EndHorizontal(); //SELECTION
EditorGUILayout.BeginHorizontal("Box"); //REPLACE
if (GUILayout.Button("Replace"))
{
DoReplace();
Close();
}
EditorGUILayout.EndHorizontal(); //REPLACE
#endregion
}
/// <summary>
/// Performs the replace operation by:
/// 1. Destroying scene object that has to be replaced.
/// 2. Instantiating prefab, settings its transform properties (parent, sibling index, localPosition, localRotation, localScale) to the deleted object's.
/// </summary>
public void DoReplace()
{
//Sanity Check: Only SCENE objects can be replaced only with PREFABS or NULL.
replaceList.RemoveAll(item => item.replace == null || EditorUtility.IsPersistent(item.replace) ||
item.with == null || !EditorUtility.IsPersistent(item.with));
//setup undo group
Undo.SetCurrentGroupName("Replace " + replaceList.Count + " items.");
int group = Undo.GetCurrentGroup();
//Iterate over list and do the replacement.
for (int i = replaceList.Count - 1; i >= 0; i--)
{
ReplaceItem(replaceList[i]);
}
Undo.CollapseUndoOperations(group);
replaceList.Clear();
}
private void ReplaceItem(Item item)
{
if (item.with == null) //check #1 -- want to replace with "NULL" a.k.a. pure delete
{
//destroy and record
Undo.DestroyObjectImmediate(item.
replace);
return;
}
var instObj = PrefabUtility.InstantiatePrefab(item.with);
var newInstance = instObj as GameObject;
if (newInstance == null) //check #2 -- the instantiated prefab is not a game object.
{
DestroyImmediate(instObj);
return;
} // this should never happen...
Undo.RegisterCreatedObjectUndo(newInstance, $"Instantiated {newInstance.name}."); //record instantiation
//store values from object's transform
var temp = item.replace.transform;
var position = temp.localPosition;
var rotation = temp.localRotation;
var scale = temp.localScale;
var parent = temp.parent;
var sibIndex = temp.GetSiblingIndex();
Undo.DestroyObjectImmediate(item.replace); //destroy replaced object and record undo
//start recording prefab instance modifications
if (PrefabUtility.IsPartOfPrefabInstance(newInstance))
PrefabUtility.RecordPrefabInstancePropertyModifications(newInstance);
//restore transform properties into instantiated
newInstance.transform.SetParent(parent);
GameObjectUtility.EnsureUniqueNameForSibling(newInstance);
newInstance.transform.localPosition = position;
newInstance.transform.localRotation = rotation;
newInstance.transform.localScale = scale;
newInstance.transform.SetSiblingIndex(sibIndex);
}
[System.Serializable]
public class Item
{
/// <summary>
/// The scene object to be replaced.
/// </summary>
public GameObject replace = null;
/// <summary>
/// The prefab that replaces scene object.
/// </summary>
public GameObject with = null;
/// <summary>
/// Convenience field for performing operations on items based on selection in editor.
/// </summary>
public bool opSelected = false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment