-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
191 additions
and
0 deletions.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
Packages/com.nekometer.esnya.esnya-unity-tools/Editor/PrefabRefinery.cs
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Reflection; | ||
using UnityEditor; | ||
using UnityEditor.UIElements; | ||
using UnityEngine; | ||
using UnityEngine.UIElements; | ||
|
||
namespace EsnyaFactory | ||
{ | ||
public class PrefabRefinery : EditorWindow | ||
{ | ||
[MenuItem("EsnyaTools/Prefab Refinery")] | ||
private static void ShowWindow() | ||
{ | ||
GetWindow<PrefabRefinery>().Show(); | ||
} | ||
|
||
private void OnEnable() | ||
{ | ||
titleContent = new GUIContent("Prefab Refinery"); | ||
|
||
Resources.Load<VisualTreeAsset>("PrefabRefinery").CloneTree(rootVisualElement); | ||
rootVisualElement.Q<Button>("revert-all").clicked += () => { | ||
Refresh(true); | ||
AssetDatabase.Refresh(); | ||
Refresh(); | ||
}; | ||
|
||
Refresh(); | ||
} | ||
|
||
private static UnityEngine.Object GetTargetObject(GameObject prefabInstanceRoot, PropertyModification mod) | ||
{ | ||
if (mod.target == null) return null; | ||
|
||
if (mod.target is GameObject) | ||
{ | ||
return prefabInstanceRoot.GetComponentsInChildren<Transform>(true) | ||
.Select(c => c.gameObject) | ||
.FirstOrDefault(o => PrefabUtility.GetCorrespondingObjectFromSource(o)?.GetInstanceID() == mod.target.GetInstanceID()); | ||
} | ||
return prefabInstanceRoot.GetComponentsInChildren(mod.target.GetType(), true) | ||
.FirstOrDefault(c => PrefabUtility.GetCorrespondingObjectFromSource(c)?.GetInstanceID() == mod.target.GetInstanceID()); | ||
} | ||
|
||
private static FieldInfo GetModificationField(UnityEngine.Object targetObject, string propertyPath) | ||
{ | ||
return targetObject?.GetType()?.GetField(propertyPath, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | ||
} | ||
|
||
private static bool PropertyEquals(UnityEngine.Object targetObject, PropertyModification mod) | ||
{ | ||
if (targetObject == null) return false; | ||
|
||
var field = GetModificationField(targetObject, mod.propertyPath); | ||
if (field == null) return false; // ToDo: array | ||
|
||
return field.GetValue(mod.target) == field.GetValue(targetObject); | ||
} | ||
|
||
private static void SetObjectField(ObjectField field, UnityEngine.Object value) | ||
{ | ||
if (field == null) return; | ||
field.objectType = value?.GetType() ?? typeof(UnityEngine.Object); | ||
field.value = value; | ||
} | ||
|
||
private void OnSelectionChange() => Refresh(); | ||
|
||
private void Refresh(bool revertAll = false) | ||
{ | ||
var modificationList = rootVisualElement.Q<VisualElement>("modifications"); | ||
var modificationTemplate = Resources.Load<VisualTreeAsset>("PrefabRefineryModification"); | ||
|
||
modificationList.Clear(); | ||
|
||
var modifications = Selection.gameObjects | ||
.Where(PrefabUtility.IsPartOfAnyPrefab) | ||
.Where(PrefabUtility.IsOutermostPrefabInstanceRoot) | ||
.Select(PrefabUtility.GetNearestPrefabInstanceRoot) | ||
.Distinct() | ||
.SelectMany(prefabInstanceRoot => | ||
(PrefabUtility.GetPropertyModifications(prefabInstanceRoot) ?? Enumerable.Empty<PropertyModification>()) | ||
.Select(modification => (modification, targetObject: GetTargetObject(prefabInstanceRoot, modification))) | ||
.Where(t => !PrefabUtility.IsDefaultOverride(t.modification) && PropertyEquals(t.targetObject, t.modification)) | ||
.Select(t => (prefabInstanceRoot, t.modification, t.targetObject)) | ||
); | ||
foreach (var (prefabInstanceRoot, modification, targetObject) in modifications) | ||
{ | ||
var item = modificationTemplate.CloneTree()[0]; | ||
SetObjectField(item.Q<ObjectField>("prefab-instance-root"), prefabInstanceRoot); | ||
SetObjectField(item.Q<ObjectField>("target-object"), targetObject); | ||
SetObjectField(item.Q<ObjectField>("target"), modification.target); | ||
item.Q<TextField>("property-path").value = modification.propertyPath; | ||
|
||
var field = GetModificationField(targetObject, modification.propertyPath); | ||
if (field?.FieldType?.IsSubclassOf(typeof(UnityEngine.Object)) ?? false) | ||
{ | ||
SetObjectField(item.Q<ObjectField>("prefab-object-reference"), field.GetValue(modification.target) as UnityEngine.Object); | ||
SetObjectField(item.Q<ObjectField>("object-reference"), field.GetValue(targetObject) as UnityEngine.Object); | ||
item.Remove(item.Q<TextField>("value")); | ||
item.Remove(item.Q<TextField>("prefab-value")); | ||
} | ||
else | ||
{ | ||
item.Q<TextField>("prefab-value").value = field.GetValue(modification.target)?.ToString() ?? "null"; | ||
item.Q<TextField>("value").value = field.GetValue(targetObject)?.ToString() ?? "null"; | ||
item.Remove(item.Q<ObjectField>("object-reference")); | ||
item.Remove(item.Q<ObjectField>("prefab-object-reference")); | ||
} | ||
|
||
void Revert() | ||
{ | ||
PrefabUtility.RevertPropertyOverride(new SerializedObject(targetObject).FindProperty(modification.propertyPath), InteractionMode.UserAction); | ||
EditorUtility.SetDirty(targetObject); | ||
} | ||
if (revertAll) Revert(); | ||
else | ||
{ | ||
item.Q<Button>("revert").clicked += () => | ||
{ | ||
Revert(); | ||
AssetDatabase.Refresh(); | ||
Refresh(); | ||
}; | ||
} | ||
modificationList.Add(item); | ||
} | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Packages/com.nekometer.esnya.esnya-unity-tools/Editor/PrefabRefinery.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
15 changes: 15 additions & 0 deletions
15
Packages/com.nekometer.esnya.esnya-unity-tools/Editor/Resources/PrefabRefinery.uxml
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements"> | ||
<uie:Toolbar style="justify-content: center;"> | ||
<ui:Label text="Prefab" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; -unity-text-align: middle-left; left: 4px;" /> | ||
<ui:Label text="Modified Object" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; -unity-text-align: middle-left; left: 4px;" /> | ||
<ui:Label text="Original Object" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; -unity-text-align: middle-left; left: 4px;" /> | ||
<ui:Label text="Property Path" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; -unity-text-align: middle-left; left: 4px;" /> | ||
<ui:Label text="Value" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; -unity-text-align: middle-left; left: 4px;" /> | ||
<ui:Label text="Overriden Value" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; -unity-text-align: middle-left; left: 4px;" /> | ||
<ui:Button name="revert-all" text="Revert" /> | ||
</uie:Toolbar> | ||
<ui:ScrollView style="flex-grow: 1;"> | ||
<ui:VisualElement name="modifications" style="flex-grow: 1; flex-shrink: 0; justify-content: flex-end;" /> | ||
<ui:Label text="Select Prefab on Hierarchy or Project Window" style="-unity-text-align: middle-center;" /> | ||
</ui:ScrollView> | ||
</ui:UXML> |
10 changes: 10 additions & 0 deletions
10
Packages/com.nekometer.esnya.esnya-unity-tools/Editor/Resources/PrefabRefinery.uxml.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
13 changes: 13 additions & 0 deletions
13
...es/com.nekometer.esnya.esnya-unity-tools/Editor/Resources/PrefabRefineryModification.uxml
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements"> | ||
<ui:VisualElement name="modification" style="min-height: 20px; height: 20px; flex-direction: row;"> | ||
<uie:ObjectField name="prefab-instance-root" style="flex-shrink: 1; flex-basis: 0; flex-grow: 1; overflow: hidden;" /> | ||
<uie:ObjectField name="target-object" style="flex-shrink: 1; flex-basis: 0; flex-grow: 1; overflow: hidden;" /> | ||
<uie:ObjectField name="target" style="flex-shrink: 1; flex-basis: 0; flex-grow: 1; overflow: hidden;" /> | ||
<ui:TextField name="property-path" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; overflow: hidden;" /> | ||
<ui:TextField name="prefab-value" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; overflow: hidden; width: 53px;" /> | ||
<uie:ObjectField name="prefab-object-reference" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; overflow: hidden;" /> | ||
<ui:TextField name="value" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; overflow: hidden; width: 53px;" /> | ||
<uie:ObjectField name="object-reference" style="flex-shrink: 1; flex-grow: 1; flex-basis: 0; overflow: hidden;" /> | ||
<ui:Button text="Revert" name="revert" /> | ||
</ui:VisualElement> | ||
</ui:UXML> |
10 changes: 10 additions & 0 deletions
10
...m.nekometer.esnya.esnya-unity-tools/Editor/Resources/PrefabRefineryModification.uxml.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.