#if UNITY_EDITOR using MonKey.Editor.Internal; using MonKey.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; #if UNITY_2022_1_OR_NEWER using UnityEditor.SceneManagement; #else #endif using UnityEngine; using Object = UnityEngine.Object; namespace MonKey.Editor.Commands { public static class PrefabUtilities { [Command("Select Prefab Root", QuickName = "SPR", Help = "Selects the prefab roots of the selected objects", DefaultValidation = DefaultValidation.AT_LEAST_ONE_GAME_OBJECT, Category = "Prefab")] public static void SelectPrefabRoots() { List roots = new List(); foreach (GameObject o in Selection.gameObjects) { roots.Add(PrefabUtility.GetOutermostPrefabInstanceRoot(o)); } Selection.objects = roots.ToArray(); } [Command("New Prefab", QuickName = "NPR", Help = "Create a prefab out of the selected objects in a folder you can specify", ValidationMethodName = "ValidateCreatePrefabs", Category = "Prefab")] public static void CreatePrefabs() { CreatePrefabs(Selection.gameObjects); } [CommandValidation("You must select at least one GameObject")] public static bool ValidateCreatePrefabs() { return (Selection.activeGameObject != null); } [Command("New Prefab Variants", "Creates prefab variants of the selected prefab", Category = "Assets", QuickName = "PV")] public static void CreatePrefabVariant(int duplicateAmount = 1) { foreach (var gameObject in Selection.gameObjects) { GameObject obj = (GameObject)PrefabUtility.InstantiatePrefab(gameObject); string path = AssetDatabase.GetAssetPath(gameObject); for (int i = 0; i < duplicateAmount; i++) { string newPath = AssetDatabase.GenerateUniqueAssetPath(path); PrefabUtility.SaveAsPrefabAsset(obj, newPath); } Object.DestroyImmediate(obj); } } private static void CreatePrefabs(GameObject[] gameObjects) { if (gameObjects == null || gameObjects.Length == 0) return; // Ask user for the asset path to store prefabs string savePath = EditorUtility.SaveFilePanelInProject("Prefab Creation", "Prefab", "prefab", "Select the prefab's folder", "Assets"); if (savePath.Length <= 0) return; if (!savePath.Contains(".prefab")) savePath += ".prefab"; int i = 0; foreach (GameObject go in gameObjects) { if (EditorUtility.DisplayCancelableProgressBar("Prefab creation", "In Progress!" + go.name, (i + .5f) / Selection.gameObjects.Length)) break; i++; string savePathArranged = savePath.Insert(savePath.IndexOf(".", StringComparison.Ordinal), "-" + go.name); PrefabUtility.SaveAsPrefabAssetAndConnect(go, savePathArranged, InteractionMode.AutomatedAction); } EditorUtility.ClearProgressBar(); } public static GameObject[] CreatePrefabDefaultObjects() { return Selection.gameObjects; } [Command("Apply Prefab", Help = "Applies the changes on the selected prefabs", QuickName = "AP", Category = "Prefab")] public static void ApplyPrefabs() { int i = 0; List parentApplied = new List(); foreach (GameObject go in Selection.gameObjects.Where(_ => _.scene.IsValid() && _.hideFlags == HideFlags.None)) { if (EditorUtility.DisplayCancelableProgressBar("Applying Prefabs", "Processing..." + go.name, (i + .5f) / Selection.gameObjects.Length)) break; i++; GameObject applied = ApplySinglePrefab(parentApplied, go); if (applied != null) parentApplied.Add(applied); } EditorUtility.ClearProgressBar(); } [CommandValidation("Select Some Prefabs First")] private static bool ValidateApplyPrefabs() { return Selection.activeGameObject != null; } internal static Object GetPrefabParentPlatformIndependant(GameObject child) { #if UNITY_2018_3_OR_NEWER GameObject prefabParent = PrefabUtility.GetCorrespondingObjectFromSource(child); #elif UNITY_2018_2_OR_NEWER Type pUtilityType = typeof(PrefabUtility); MethodInfo info = pUtilityType.GetMethod("GetCorrespondingObjectFromSource"); //stupid ass reflection because of some method signature change in the last Unity GameObject prefabParent = info.Invoke(null,new object[]{child}) as GameObject; #else GameObject prefabParent = PrefabUtility.GetPrefabParent(child) as GameObject; #endif return prefabParent; } [Command("Select Instances", "Selects all instances of the specified prefab", QuickName = "SI", AlwaysShow = true, Order = -5, MenuItemLink = "SelectPrefabInstances", MenuItemLinkTypeOwner = "MonkeyMenuItems", Category = "Prefab")] public static void SelectInstancesOfPrefab( [CommandParameter(Help = "The prefab to find the instances of on the opened scenes", ForceAutoCompleteUsage = true, DefaultValueNameOverride = "Prefab Selected", AutoCompleteMethodName = "PrefabAutoComplete")] string prefabName = "") { GameObject prefab; if (prefabName == "") prefab = Selection.objects.First(_ => _ is GameObject) as GameObject; else prefab = AssetDatabase.LoadAssetAtPath(prefabName); if (prefab == null) { Debug.LogWarning("Monkey Error: the prefab was not recognized"); return; } EditorUtility.DisplayProgressBar("Finding Prefab Instances...", "Please Wait while Monkey is looking for the prefab instances!", 0); List transs = TransformExt.GetAllTransformedOrderUpToDown(); int i = 0; List selectedInstances = new List(); foreach (Transform transform in transs) { if (EditorUtility.DisplayCancelableProgressBar("Finding Prefab Instances...", "Please Wait while Monkey is looking for the prefab instances!", ((float)i / transs.Count) * .5f + .5f)) break; if (prefab.IsPrefab()) { #if UNITY_2018_3_OR_NEWER GameObject prefabRoot = PrefabUtility.GetNearestPrefabInstanceRoot(transform.gameObject); #else GameObject prefabRoot = PrefabUtility.FindPrefabRoot(transform.gameObject); #endif // Update the prefab of the game object Object prefabParent = prefabRoot ? GetPrefabParentPlatformIndependant(prefabRoot) : null; if (prefabParent == prefab && !selectedInstances.Contains(prefabRoot)) selectedInstances.Add(prefabRoot); } else { //then it's a model MeshFilter filter = transform.gameObject.GetComponent(); if (filter) { MeshFilter[] filters = prefab.GetComponentsInChildren(); if (filters.Any(_ => _.sharedMesh == filter.sharedMesh)) selectedInstances.Add(filter.gameObject); } } i++; } Selection.objects = selectedInstances.Convert(_ => _ as Object).ToArray(); EditorUtility.ClearProgressBar(); } public static AssetNameAutoComplete PrefabAutoComplete() { return new AssetNameAutoComplete() { CustomType = "GameObject" }; } public static GameObject ApplySinglePrefab(List excludedPrefabs, GameObject gameObject, bool forceActive = false, bool resetTransform = false) { GameObject prefabRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject); // Update the prefab of the game object GameObject prefabParent = (GameObject)GetPrefabParentPlatformIndependant(prefabRoot); if (excludedPrefabs.Contains(prefabParent)) return null; if (prefabParent != null) { bool isActive = gameObject.activeSelf; Transform t = prefabRoot.transform; Vector3 localPosition = t.localPosition; Quaternion localRotation = t.localRotation; if (forceActive) gameObject.SetActive(true); if (resetTransform) { t.localPosition = Vector3.zero; t.localRotation = Quaternion.identity; } PrefabUtility.SavePrefabAsset(prefabRoot); if (forceActive) gameObject.SetActive(isActive); if (resetTransform) { t.localPosition = localPosition; t.localRotation = localRotation; } return prefabParent; } return null; } [Command("Revert Prefab Instances", "Reverts all the changes made to the prefab instance to the base prefab", QuickName = "RPI", DefaultValidation = DefaultValidation.AT_LEAST_ONE_GAME_OBJECT, Category = "Prefab")] public static void RevertToPrefabInstances() { foreach (GameObject o in Selection.gameObjects) { if (PrefabUtility.GetPrefabInstanceStatus(o) != PrefabInstanceStatus.NotAPrefab) PrefabUtility.RevertObjectOverride(o, InteractionMode.AutomatedAction); } } #if UNITY_2018_3_OR_NEWER || UNITY_2019 [Command("Open Prefab", "Opens a prefab", AlwaysShow = true, Category = "Prefab", QuickName = "OP", DefaultValidation = DefaultValidation.IN_EDIT_MODE)] private static void OpenPrefab([CommandParameter(AutoCompleteMethodName = "PrefabNameAutoComplete", ForceAutoCompleteUsage = true, Help = "The name of the prefab to open", OverrideName = "Prefab Name", PreventDefaultValueUsage = true)] string prefabPath) { Type utils = typeof(UnityEditor.SceneManagement.PrefabStageUtility); var methods = utils.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (methods.Length == 0) Debug.Log("The Open Prefab Method wasn't found: Unity may have changed its API, " + "please contact the MonKey support"); foreach (var info in methods) { if (info.Name == "OpenPrefab" && info.GetParameters().Length == 1) { info.Invoke(null, new object[] { prefabPath }); return; } } } private static AssetNameAutoComplete PrefabNameAutoComplete() { return new AssetNameAutoComplete() { CustomType = "GameObject", IncludeDirectories = false, PopulateOnInit = true, }; } [Command("Remove Component Of Type From Prefabs In Folder", Help = "Removes all the component of the given type on all the prefabs present in a given folder, recursively")] public static void RemoveComponentsFromPrefabsInFolder( [CommandParameter(AutoCompleteMethodName = "FolderAutoComplete", Help = "The name of the folder", ForceAutoCompleteUsage = true)] string folderName, [CommandParameter(Help = "The Type of Component", AutoCompleteMethodName = "ComponentTypeAuto")] Type componentType) { SelectionUtilities.FindFolder(folderName); string folderPath = AssetDatabase.GetAssetPath(Selection.activeObject); string[] folders = AssetDatabase.GetSubFolders(folderName); List foldersList = new List(folders); foldersList.Add(folderName); string[] prefabPaths = AssetDatabase.FindAssets("t:GameObject", foldersList.ToArray()); foreach (var path in prefabPaths) { var go = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(path)); if (!go) return; var instance = (GameObject)PrefabUtility.InstantiatePrefab(go); var comps = instance.GetComponentsInChildren(componentType); var originalComps = go.GetComponentsInChildren(componentType); for (var index = 0; index < comps.Length; index++) { var component = comps[index]; var prefabComponent = originalComps[index]; Object.DestroyImmediate(component); PrefabUtility.ApplyRemovedComponent(instance, prefabComponent, InteractionMode.AutomatedAction); } Object.DestroyImmediate(instance); } AssetDatabase.SaveAssets(); } public static AssetNameAutoComplete FolderAutoComplete() { return new AssetNameAutoComplete() { DirectoryMode = true }; } public static TypeAutoComplete ComponentTypeAuto() { return new TypeAutoComplete(false, true, true, false, false); } #endif } } #endif