Last active
June 7, 2020 18:16
-
-
Save h-sigma/461bd8e4645dd02ea5975ce38172d705 to your computer and use it in GitHub Desktop.
A
This file contains 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 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