379 lines
14 KiB
C#
379 lines
14 KiB
C#
|
#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<Object> roots = new List<Object>();
|
|||
|
|
|||
|
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<GameObject> parentApplied = new List<GameObject>();
|
|||
|
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<GameObject>(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<Transform> transs = TransformExt.GetAllTransformedOrderUpToDown();
|
|||
|
int i = 0;
|
|||
|
|
|||
|
List<GameObject> selectedInstances = new List<GameObject>();
|
|||
|
|
|||
|
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<MeshFilter>();
|
|||
|
if (filter)
|
|||
|
{
|
|||
|
MeshFilter[] filters = prefab.GetComponentsInChildren<MeshFilter>();
|
|||
|
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<GameObject> 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<string> foldersList = new List<string>(folders);
|
|||
|
|
|||
|
foldersList.Add(folderName);
|
|||
|
|
|||
|
string[] prefabPaths = AssetDatabase.FindAssets("t:GameObject", foldersList.ToArray());
|
|||
|
foreach (var path in prefabPaths)
|
|||
|
{
|
|||
|
var go = AssetDatabase.LoadAssetAtPath<GameObject>(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
|