ReplaceSelected.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEditor;
  4. using UnityEditor.SceneManagement;
  5. using UnityEngine;
  6. // Staggart Creations http://staggart.xyz
  7. // Copyright protected under Unity asset store EULA
  8. public class ReplaceSelected : EditorWindow
  9. {
  10. #if UNITY_2019_3_OR_NEWER
  11. private const float HEIGHT = 265f;
  12. #else
  13. private const float HEIGHT = 250f;
  14. #endif
  15. [MenuItem("GameObject/Replace selected")]
  16. public static void OpenWindow()
  17. {
  18. //Show existing window instance. If one doesn't exist, make one.
  19. ReplaceSelected window = (ReplaceSelected)EditorWindow.GetWindow(typeof(ReplaceSelected), true);
  20. //Options
  21. window.autoRepaintOnSceneChange = true;
  22. window.maxSize = new Vector2(230f, HEIGHT);
  23. window.minSize = window.maxSize;
  24. window.titleContent.image = EditorGUIUtility.IconContent("GameObject Icon").image;
  25. window.titleContent.text = "Replace selected";
  26. window.Show();
  27. }
  28. [SerializeField]
  29. private Object sourceObject;
  30. private static Object sourcePrefab;
  31. private Texture refreshIcon;
  32. private void OnEnable()
  33. {
  34. refreshIcon = EditorGUIUtility.IconContent("Refresh").image;
  35. if (sourceObject != null) return;
  36. if (LastTargetGUID != string.Empty)
  37. {
  38. string path = AssetDatabase.GUIDToAssetPath(LastTargetGUID);
  39. sourceObject = (Object)AssetDatabase.LoadAssetAtPath(path, typeof(Object));
  40. }
  41. }
  42. private static string LastTargetGUID
  43. {
  44. get { return EditorPrefs.GetString(PlayerSettings.productName + "_REPLACE_LASTGUID", string.Empty); }
  45. set { EditorPrefs.SetString(PlayerSettings.productName + "_REPLACE_LASTGUID", value); }
  46. }
  47. private static bool KeepScale
  48. {
  49. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepScale", true); }
  50. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepScale", value); }
  51. }
  52. private static bool KeepRotation
  53. {
  54. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepRotation", true); }
  55. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepRotation", value); }
  56. }
  57. private static bool KeepName
  58. {
  59. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepName", false); }
  60. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepName", value); }
  61. }
  62. private static bool KeepPrefabOverrides
  63. {
  64. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepPrefabOverrides", false); }
  65. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepPrefabOverrides", value); }
  66. }
  67. private static bool KeepLayer
  68. {
  69. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepLayer", false); }
  70. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepLayer", value); }
  71. }
  72. private static bool KeepTag
  73. {
  74. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepTag", false); }
  75. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepTag", value); }
  76. }
  77. private static bool KeepStaticFlags
  78. {
  79. get { return EditorPrefs.GetBool(PlayerSettings.productName + "_REPLACE_KeepStaticFlags", false); }
  80. set { EditorPrefs.SetBool(PlayerSettings.productName + "_REPLACE_KeepStaticFlags", value); }
  81. }
  82. private static bool SupportsUndo()
  83. {
  84. #if UNITY_2021_2_OR_NEWER
  85. //A bug in this version causes a crash when performing a redo operation while editing a prefab
  86. if (UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() != null) return false;
  87. return true;
  88. #else
  89. return true;
  90. #endif
  91. }
  92. private void OnGUI()
  93. {
  94. if (Selection.gameObjects.Length == 0)
  95. {
  96. EditorGUILayout.HelpBox("Nothing selected", MessageType.Info);
  97. return;
  98. }
  99. EditorGUILayout.LabelField("Replacement object/prefab", EditorStyles.boldLabel);
  100. EditorGUI.BeginChangeCheck();
  101. sourceObject = (Object)EditorGUILayout.ObjectField(sourceObject, typeof(GameObject), true);
  102. if (EditorGUI.EndChangeCheck())
  103. {
  104. LastTargetGUID = sourceObject ? AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(sourceObject.GetInstanceID())) : string.Empty;
  105. }
  106. EditorGUILayout.Space();
  107. using (new EditorGUI.DisabledGroupScope(sourceObject == null))
  108. {
  109. EditorGUILayout.LabelField("Keep selection's", EditorStyles.boldLabel);
  110. KeepScale = EditorGUILayout.Toggle(new GUIContent("Scale", "Enable to keep the current object's scale"), KeepScale);
  111. KeepRotation = EditorGUILayout.Toggle(new GUIContent("Rotation", "Enable to keep the current object's rotation"), KeepRotation);
  112. KeepName = EditorGUILayout.Toggle(new GUIContent("Name", "Enable to keep the current object's name"), KeepName);
  113. KeepLayer = EditorGUILayout.Toggle(new GUIContent("Layer", "Enable to keep the current object's layer"), KeepLayer);
  114. KeepTag = EditorGUILayout.Toggle(new GUIContent("Tag", "Enable to keep the current object's tag"), KeepTag);
  115. KeepStaticFlags = EditorGUILayout.Toggle(new GUIContent("Static flags", "Enable to keep the current object's static flags"), KeepStaticFlags);
  116. KeepPrefabOverrides = EditorGUILayout.Toggle(new GUIContent("Prefab overrides", "If the selected- and replacement objects are a prefab, overrides are copied over to the replaced object"), KeepPrefabOverrides);
  117. EditorGUILayout.Space();
  118. if (GUILayout.Button(new GUIContent(" Replace " + Selection.gameObjects.Length + " GameObject" + (Selection.gameObjects.Length > 1 ? "s" : ""), refreshIcon), GUILayout.Height(25f)))
  119. {
  120. ReplaceCurrentSelection();
  121. }
  122. }
  123. }
  124. private void OnSelectionChange()
  125. {
  126. this.Repaint();
  127. }
  128. private void ReplaceCurrentSelection()
  129. {
  130. if (Selection.gameObjects.Length == 0 || sourceObject == null) return;
  131. if (PrefabUtility.GetPrefabAssetType(sourceObject) == PrefabAssetType.Variant)
  132. {
  133. //PrefabUtility.GetCorrespondingObjectFromSource still returns the base prefab. However, this does work.
  134. sourcePrefab = sourceObject;
  135. }
  136. else
  137. {
  138. sourcePrefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(sourceObject);
  139. }
  140. //Model prefabs don't count
  141. var isPrefab = sourcePrefab;
  142. foreach (GameObject selected in Selection.gameObjects)
  143. {
  144. Replace(sourceObject, selected, isPrefab);
  145. }
  146. }
  147. private static void Replace(Object source, GameObject target, bool sourceIsPrefab)
  148. {
  149. //Skip anything selected in the project window!
  150. if (target.scene.IsValid() == false) return;
  151. GameObject newObj = null;
  152. if (PrefabUtility.IsPartOfPrefabInstance(target) && !PrefabUtility.IsOutermostPrefabInstanceRoot(target))
  153. {
  154. Debug.LogError("Cannot replace an object that's part of a prefab instance. It must be unpacked first.", target);
  155. return;
  156. }
  157. if (sourceIsPrefab)
  158. {
  159. newObj = PrefabUtility.InstantiatePrefab(sourcePrefab, target.scene) as GameObject;
  160. if(SupportsUndo()) Undo.RegisterCreatedObjectUndo(newObj, "Replaced with prefabs");
  161. //Apply any overrides (added/removed components, parameters, etc)
  162. if (KeepPrefabOverrides)
  163. {
  164. if (PrefabUtility.HasPrefabInstanceAnyOverrides(target, false))
  165. {
  166. CopyPrefabOverrides(target, newObj);
  167. }
  168. }
  169. }
  170. else
  171. {
  172. newObj = GameObject.Instantiate(source) as GameObject;
  173. if(SupportsUndo()) Undo.RegisterCreatedObjectUndo(newObj, "Replaced object");
  174. newObj.name = newObj.name.Replace("(Clone)", string.Empty);
  175. }
  176. newObj.transform.parent = target.transform.parent;
  177. newObj.transform.SetSiblingIndex(target.transform.GetSiblingIndex());
  178. newObj.transform.position = target.transform.position;
  179. if (KeepName) newObj.name = target.name;
  180. if (KeepRotation) newObj.transform.rotation = target.transform.rotation;
  181. if (KeepScale) newObj.transform.localScale = target.transform.localScale;
  182. if (KeepTag) newObj.tag = target.tag;
  183. if (KeepLayer) newObj.layer = target.layer;
  184. if (KeepStaticFlags) GameObjectUtility.SetStaticEditorFlags(newObj, GameObjectUtility.GetStaticEditorFlags(target));
  185. if (Selection.gameObjects.Length == 1) Selection.activeGameObject = newObj;
  186. EditorSceneManager.MarkSceneDirty(target.scene);
  187. //Remove the original object (can cause a crash if it is a prefab due to a Unity bug (2019.3))
  188. if(SupportsUndo()) Undo.DestroyObjectImmediate(target);
  189. }
  190. private static void CopyPrefabOverrides(GameObject source, GameObject target)
  191. {
  192. //Model prefab don't have real overrides, copy any components instead
  193. if (PrefabUtility.GetPrefabAssetType(target) == PrefabAssetType.Model)
  194. {
  195. Debug.Log(target.name + " is a model prefab");
  196. CopyComponents(target, source);
  197. return;
  198. }
  199. //Get all overrides
  200. PropertyModification[] overrides = PrefabUtility.GetPropertyModifications(source);
  201. List<AddedComponent> added = PrefabUtility.GetAddedComponents(source);
  202. List<RemovedComponent> removed = PrefabUtility.GetRemovedComponents(source);
  203. //Remove any components removed as an overrides
  204. for (int i = 0; i < removed.Count; i++)
  205. {
  206. Component comp = target.GetComponent(removed[i].assetComponent.GetType());
  207. DestroyImmediate(comp);
  208. }
  209. //Add any components added as overrides and copy the values over
  210. for (int i = 0; i < added.Count; i++)
  211. {
  212. Component copy = target.GetComponent(added[i].instanceComponent.GetType());
  213. if(!copy) copy = target.AddComponent(added[i].instanceComponent.GetType());
  214. EditorUtility.CopySerialized(added[i].instanceComponent, copy);
  215. }
  216. //PrefabUtility.ApplyPrefabInstance(target, InteractionMode.AutomatedAction);
  217. //Apply any modified parameters
  218. PrefabUtility.SetPropertyModifications(target, overrides);
  219. }
  220. private static void CopyComponents(GameObject source, GameObject destination)
  221. {
  222. Component[] sourceComponents = destination.GetComponents(typeof(Component));
  223. for (int i = 0; i < sourceComponents.Length; i++)
  224. {
  225. //Don't copy Transform or GameObject values
  226. if(sourceComponents[i].GetType() == typeof(Transform) || sourceComponents[i].GetType() == typeof(GameObject)) continue;
  227. Component newComp = source.GetComponent(sourceComponents[i].GetType());
  228. if (!newComp)
  229. {
  230. newComp = source.AddComponent(sourceComponents[i].GetType());
  231. }
  232. //Copy over values, on prefabs these will automatically becomes overrides
  233. if (newComp)
  234. {
  235. UnityEditorInternal.ComponentUtility.CopyComponent(sourceComponents[i]);
  236. UnityEditorInternal.ComponentUtility.PasteComponentValues(newComp);
  237. }
  238. }
  239. }
  240. }