Add "Dirt" plugin

This commit is contained in:
admin committed 2026-07-01 13:07:16 +02:00
1 parent c2d535c661
commit 8567b9d8ef
23 files changed
+1202

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3b5401099ed7f1049a6f1339094e387e, type: 3}
m_Name: DirtWindowSettings
m_EditorClassIdentifier: Dirt::Dirt.DirtWindowSettings
modificationLimit: {x: 3, y: 10}
visibility: 3
exclusions: []
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 949cf3c297246c54593be19eac457770
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8484dc58e4e6dec4d8bbec756fa758bb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+16
View File
@@ -0,0 +1,16 @@
{
"name": "Dirt",
"rootNamespace": "",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4e2a065fa28e72148b0fa49a1061dba9
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement name="InstanceOwnerHierarchy" style="flex-direction: row; align-items: center; margin-top: 10px; margin-bottom: 2px; padding-top: 1px; padding-right: 0; padding-bottom: 1px; padding-left: 0; margin-right: 5px; margin-left: 5px;">
<!-- <uie:ObjectField name="InstanceOwner" style="flex-shrink: 1; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0;" /> -->
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 872d329bdd85298469aecb4acce3d6f2
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
+13
View File
@@ -0,0 +1,13 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<ui:VisualElement style="flex-direction: row; align-items: center; margin-left: 5px; margin-right: 5px; margin-top: 1px; margin-bottom: 1px;">
<uie:ObjectField name="InstanceTarget" style="flex-basis: 260px; margin-top: 0; margin-bottom: 0;" />
<ui:Label name="PropertyPath" style="text-overflow: ellipsis; overflow: hidden; margin-top: 0; margin-bottom: 0; flex-grow: 1; flex-shrink: 1; flex-basis: 50%;" />
<ui:VisualElement style="flex-direction: row; overflow: hidden; align-items: center; flex-grow: 1; flex-basis: 400px;">
<ui:Label name="PrefabValue" text="(nothing)" style="text-overflow: ellipsis; overflow: hidden; margin-top: 0; margin-bottom: 0; -unity-text-align: upper-right; flex-basis: 180px; flex-shrink: 1; flex-grow: 1;" />
<uie:ObjectField name="PrefabObjectValue" style="margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 0; flex-basis: 180px; flex-shrink: 1; flex-grow: 1;" />
<ui:Label text="►" style="color: rgba(255, 255, 255, 0.2); flex-basis: 20px; -unity-text-align: upper-center;" />
<ui:Label name="InstanceValue" text="(nothing)" style="text-overflow: ellipsis; overflow: hidden; -unity-font-style: bold; margin-top: 0; margin-bottom: 0; flex-basis: 180px; flex-shrink: 1; flex-grow: 1;" />
<uie:ObjectField name="InstanceObjectValue" style="margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 0; -unity-font-style: bold; flex-basis: 180px; flex-shrink: 1; flex-grow: 1;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e96daeb60bacf3d48b0e9e4ccbbe167c
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
+517
View File
@@ -0,0 +1,517 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
namespace Dirt
{
public class DirtWindow : EditorWindow
{
[SerializeField] VisualTreeAsset visualTreeAsset;
[SerializeField] VisualTreeAsset headerAsset;
[SerializeField] VisualTreeAsset rowAsset;
[MenuItem("Tools/Dirt")]
public static void Open() => GetWindow<DirtWindow>("Dirt", typeof(SceneView));
string activePath;
int refreshCountdown;
TextField searchField;
ScrollView content;
readonly List<List<Modification>> cache = new();
readonly List<Modification> visibleModifications = new();
int cacheIndexGUI;
public void CreateGUI()
{
DirtWindowSettings.Load();
if (!visualTreeAsset)
{
return;
}
var templateContainer = visualTreeAsset.Instantiate();
rootVisualElement.Add(templateContainer);
templateContainer.StretchToParentSize();
searchField = rootVisualElement.Q<TextField>("Search");
searchField.RegisterValueChangedCallback(e => refreshCountdown = 3);
content = rootVisualElement.Q<ScrollView>("Content");
content.RegisterCallback<MouseDownEvent>(e => OnMouseDown(e));
refreshCountdown = 2;
}
void Update()
{
var text = "Dirt";
if (cache.Count > 0)
{
var progress = Mathf.RoundToInt(
(float) cacheIndexGUI / cache.Count * 100
);
if (progress < 100)
{
text += $" - {progress}%";
}
}
if (text == "Dirt" && titleContent.text != text)
{
Repaint();
}
titleContent.text = text;
if (refreshCountdown > 0)
{
return;
}
UpdateGUI();
}
void OnInspectorUpdate()
{
var activePath = GetActivePath(out _);
if (this.activePath != activePath)
{
this.activePath = activePath;
refreshCountdown += 2;
}
if (refreshCountdown != 0)
{
if (refreshCountdown == 1)
{
this.activePath = GetActivePath(out var prefabStage);
var instanceOwners = FindInstanceOwners(prefabStage);
RefreshCache(instanceOwners);
ClearGUI();
}
refreshCountdown--;
}
}
static string GetActivePath(out PrefabStage prefabStage)
{
prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage)
{
return prefabStage.assetPath;
}
else
{
return string.Join(";", Enumerable
.Range(0, SceneManager.sceneCount)
.Select(i => SceneManager.GetSceneAt(i).path)
);
}
}
static List<GameObject> FindInstanceOwners(PrefabStage prefabStage)
{
return (
prefabStage
? prefabStage
.FindComponentsOfType<Transform>()
.Where(x => x.hideFlags != HideFlags.HideAndDontSave)
.Select(x => x.gameObject)
: FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.InstanceID)
.AsEnumerable()
).ToList();
}
void RefreshCache(List<GameObject> instanceOwners)
{
cache.Clear();
foreach (var instanceOwner in instanceOwners.Where(PrefabUtility.IsOutermostPrefabInstanceRoot))
{
var prefabOwner = PrefabUtility.GetCorrespondingObjectFromSource(instanceOwner);
cache.Add(
PrefabUtility.GetPropertyModifications(instanceOwner)
.Select(x => new Modification(prefabOwner, instanceOwner, x))
.Where(x => x.instance is not null)
.ToList()
);
}
}
void ClearGUI()
{
content.Clear();
visibleModifications.Clear();
cacheIndexGUI = 0;
}
void UpdateGUI()
{
if (cacheIndexGUI >= cache.Count)
{
return;
}
var settings = DirtWindowSettings.instance;
foreach (var modifications in cache.Skip(cacheIndexGUI))
{
cacheIndexGUI++;
var filteredModifications = modifications.Where(x =>
{
var s = searchField.value;
if (s == string.Empty)
{
return true;
}
var i = StringComparison.InvariantCultureIgnoreCase;
foreach (var value in x.instance.GetOwnerHierarchy())
{
if (value.name.Contains(s, i))
{
return true;
}
}
var t = x.instance.GetTarget();
if (t && (t.name.Contains(s, i) || t.GetType().Name.Contains(s, i)))
{
return true;
}
if (x.propertyPath.Contains(s, i))
{
return true;
}
if (x.prefab.targetPropertyValue.Contains(s, i))
{
return true;
}
if (x.instance.targetPropertyValue.Contains(s, i))
{
return true;
}
return false;
}).Where(x => x.IsUnchangedButDirty()
? settings.visibility.HasFlag(VisibilityMask.UnchangedDirtyValues)
: settings.visibility.HasFlag(VisibilityMask.ChangedDirtyValues)
).ToList();
if (filteredModifications.Count == 0)
{
continue;
}
visibleModifications.AddRange(filteredModifications);
var header = headerAsset.Instantiate();
content.Add(header);
var instanceOwnerHierarchyContainer = header.Q<VisualElement>("InstanceOwnerHierarchy");
var fields = filteredModifications
.First()
.instance
.GetOwnerHierarchy()
.Select(x => new ObjectField
{
value = x,
style = {
flexShrink = 1,
marginTop = 10,
marginRight = 0,
marginBottom = 0,
marginLeft = 0
}
});
foreach (var field in fields)
{
if (instanceOwnerHierarchyContainer.childCount > 0)
{
instanceOwnerHierarchyContainer.Add(new Label
{
text = "/",
style = {
marginTop = 10,
marginRight = 2,
marginBottom = 0,
marginLeft = 2
}
});
}
instanceOwnerHierarchyContainer.Add(field);
}
var rowContent = new VisualElement();
content.Add(rowContent);
if (settings.modificationLimit.y > 0 && filteredModifications.Count <= settings.modificationLimit.y)
{
foreach (var m in filteredModifications)
{
rowContent.Add(CreateRow(m));
}
break;
}
foreach (var m in filteredModifications.Take(settings.modificationLimit.x))
{
rowContent.Add(CreateRow(m));
}
var moreModifications = filteredModifications
.Skip(settings.modificationLimit.x)
.ToList();
var button = new Button
{
text = $"Show More... ({moreModifications.Count})"
};
button.AddToClassList("link");
button.clicked += () =>
{
button.RemoveFromHierarchy();
foreach (var m in moreModifications)
{
rowContent.Add(CreateRow(m));
}
};
rowContent.Add(button);
break;
}
}
VisualElement CreateRow(Modification modification)
{
var row = rowAsset.Instantiate();
row.AddToClassList("row");
row.RegisterCallback<MouseDownEvent>(e => OnMouseDown(e, row, modification));
row.Q<ObjectField>("InstanceTarget").value = modification.instance.GetTarget();
foreach (var (name, reference) in new[] {
("Prefab", modification.prefab),
("Instance", modification.instance)
})
{
var label = row.Q<Label>($"{name}Value");
var objectField = row.Q<ObjectField>($"{name}ObjectValue");
if (reference.hasTargetPropertyObjectValue)
{
label.RemoveFromHierarchy();
objectField.value = reference.targetPropertyObjectValue;
}
else
{
label.text = reference.targetPropertyValue;
objectField.RemoveFromHierarchy();
}
}
var propertyPathLabel = row.Q<Label>("PropertyPath");
propertyPathLabel.text = modification.propertyPath;
RefreshRowAppeareance(row, modification);
return row;
}
void RefreshRowAppeareance(VisualElement row, Modification modification)
{
if (modification.excluded)
{
row.AddToClassList("excluded");
}
else
{
row.RemoveFromClassList("excluded");
}
if (modification.IsUnchangedButDirty())
{
row.AddToClassList("unchanged-but-dirty");
}
else
{
row.RemoveFromClassList("unchanged-but-dirty");
}
}
void OnMouseDown(MouseDownEvent e, VisualElement row = null, Modification modification = null)
{
if (e.button != 1)
{
return;
}
var settings = DirtWindowSettings.instance;
if (row is not null)
{
e.StopPropagation();
}
var menu = new GenericMenu();
if (row is not null)
{
menu.AddItem(
content: new("Revert"),
on: false,
func: x =>
{
(x as Modification).Revert();
row.SetEnabled(false);
},
userData: modification
);
}
var modifications = visibleModifications
.Where(x => x.IsUnchangedButDirty())
.ToList();
if (modifications.Count > 0)
{
menu.AddItem(new($"Revert Unchanged Values ({modifications.Count})"), false, () =>
{
if (
EditorUtility.DisplayDialog(
"Warning",
$"Do you really want to revert {modifications.Count} values?",
"Yes",
"No"
)
)
{
foreach (var modification in modifications)
{
modification.Revert();
}
refreshCountdown = 1;
}
});
}
if (row is not null)
{
menu.AddItem(
content: new("Apply"),
on: false,
func: x =>
{
(x as Modification).Apply();
row.SetEnabled(false);
},
userData: modification
);
if (modification.excluded)
{
menu.AddDisabledItem(
content: new("Excluded"),
on: true
);
}
else
{
menu.AddItem(
content: new("Exclude"),
on: false,
func: x =>
{
var m = x as Modification;
settings.ToggleExclusion(m);
RefreshRowAppeareance(row, m);
},
userData: modification
);
}
}
if (row is not null || modifications.Count > 0)
{
menu.AddSeparator("");
}
menu.AddItem(
content: new("Refresh"),
on: false,
func: () => refreshCountdown = 1
);
menu.AddSeparator("");
foreach (var (x, y) in new[] {
(VisibilityMask.ChangedDirtyValues, "Changed Dirty Values"),
(VisibilityMask.UnchangedDirtyValues, "Unchanged Dirty Values"),
(VisibilityMask.Exclusions, "Exclusions")
})
{
var on = settings.visibility.HasFlag(x);
menu.AddItem(
content: new($"Show {y}"),
on,
func: () =>
{
if (on)
{
settings.visibility ^= x;
}
else
{
settings.visibility |= x;
}
refreshCountdown = 1;
}
);
}
menu.AddSeparator("");
menu.AddItem(
content: new("Open Settings"),
on: false,
func: () =>
{
var activeObject = Selection.activeObject;
Selection.activeObject = settings;
EditorApplication.ExecuteMenuItem("Assets/Properties...");
Selection.activeObject = activeObject;
}
);
menu.ShowAsContext();
}
}
}
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: b13b7b6e39ea0774996f0d40477f4aec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- m_ViewDataDictionary: {instanceID: 0}
- visualTreeAsset: {fileID: 9197481963319205126, guid: affec65379c5c4d4e934ab8463f489e4, type: 3}
- headerAsset: {fileID: 9197481963319205126, guid: 872d329bdd85298469aecb4acce3d6f2, type: 3}
- rowAsset: {fileID: 9197481963319205126, guid: e96daeb60bacf3d48b0e9e4ccbbe167c, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+66
View File
@@ -0,0 +1,66 @@
EditorPanelRootElement > VisualElement > TemplateContainer {
background-color: #333;
}
#Content {
background-color: #383838;
border-bottom-width: 1px;
border-bottom-color: #292929;
}
#Search #unity-text-input {
padding: 3px;
}
#Content #unity-content-container {
padding-bottom: 10px;
}
Button.link {
align-self: flex-start;
margin-top: 2px;
margin-right: 0px;
margin-left: 273px;
margin-bottom: 2px;
padding-top: 0px;
padding-right: 1px;
padding-bottom: 0px;
padding-left: 1px;
background-color: transparent;
border-top-width: 0px;
border-right-width: 0px;
border-bottom-width: 1px;
border-bottom-color: transparent;
border-left-width: 0;
color: rgb(105, 205, 255);
}
Button.link:hover {
color: rgb(155, 205, 255);
border-bottom-color: rgb(155, 205, 255);
}
.row {
color: rgb(205, 205, 205);
}
.row:hover {
background-color: rgba(255, 255, 255, .1);
}
.row.unchanged-but-dirty {
color: rgb(255, 255, 255);
background-color: rgba(155, 255, 155, .1);
}
.row.unchanged-but-dirty:hover {
background-color: rgba(155, 255, 155, .2);
}
.row.excluded {
color: rgba(255, 155, 155, .5);
}
.row.excluded:hover {
background-color: rgba(255, 205, 205, .05);
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0ed142f8c193d8544995bb04fd255893
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
@@ -0,0 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<Style src="project://database/Assets/Dirt/DirtWindow.uss?fileID=7433441132597879392&amp;guid=0ed142f8c193d8544995bb04fd255893&amp;type=3#DirtWindow" />
<ui:TextField placeholder-text="Search" name="Search" style="margin-right: 2px; margin-left: 2px;" />
<ui:ScrollView name="Content" style="flex-grow: 1; height: 385px;" />
</ui:UXML>
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: affec65379c5c4d4e934ab8463f489e4
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace Dirt
{
public class DirtWindowSettings : ScriptableObject
{
public static DirtWindowSettings instance { get; private set; }
public static void Load([CallerFilePath] string path = null)
{
path = Path.Combine("Assets", $"{nameof(DirtWindowSettings)}.asset");
instance = AssetDatabase.LoadAssetAtPath<DirtWindowSettings>(path);
if (instance)
return;
instance = CreateInstance<DirtWindowSettings>();
AssetDatabase.CreateAsset(instance, path);
AssetDatabase.Refresh();
}
public Vector2Int modificationLimit = new(3, 10);
public VisibilityMask visibility
= VisibilityMask.ChangedDirtyValues
| VisibilityMask.UnchangedDirtyValues;
[Serializable]
public class Exclusion
{
public GameObject owner;
public string targetType;
public bool useTargetPath;
public string targetPath;
public string propertyPath;
public bool propertyPathStartsWith;
public bool propertyPathRegex;
public bool Match(Modification modification)
{
if (owner)
{
var prefabOwner = modification.prefab.GetOwner();
if (prefabOwner && prefabOwner != owner)
return false;
}
if (targetType != string.Empty)
{
var prefabTarget = modification.prefab.GetTarget();
if (prefabTarget)
{
var prefabType = prefabTarget.GetType().FullName;
if (prefabType != targetType)
return false;
}
}
if (useTargetPath)
{
var prefabTargetPath = modification.prefab.targetPath;
if (prefabTargetPath != targetPath)
return false;
}
if (propertyPath != string.Empty)
{
if (propertyPathRegex)
{
if (!Regex.IsMatch(modification.propertyPath, propertyPath))
return false;
}
else if (propertyPathStartsWith)
{
if (!modification.propertyPath.StartsWith(propertyPath))
return false;
}
else if (modification.propertyPath != propertyPath)
{
return false;
}
}
return true;
}
}
[SerializeField] List<Exclusion> exclusions = new();
public void ToggleExclusion(Modification modification)
{
if (modification.excluded)
{
modification.excluded = false;
exclusions.RemoveAll(x => x.Match(modification));
return;
}
var target = modification.prefab.GetTarget();
modification.excluded = true;
exclusions.Add(new()
{
owner = modification.prefab.GetOwner(),
targetType = target ? target.GetType().FullName : string.Empty,
useTargetPath = true,
targetPath = modification.prefab.targetPath,
propertyPath = modification.propertyPath
});
}
public bool IsExcluded(Modification modification)
{
return exclusions.Any(x => x.Match(modification));
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3b5401099ed7f1049a6f1339094e387e
+127
View File
@@ -0,0 +1,127 @@
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Dirt
{
public class Modification
{
public string propertyPath;
public ModificationReference instance;
public ModificationReference prefab;
public bool excluded;
bool appliedOrReverted;
public Modification(GameObject prefabOwner, GameObject instanceOwner, PropertyModification propertyModification)
{
propertyPath = propertyModification.propertyPath;
prefab = new(prefabOwner, propertyModification.target);
var componentIndex = prefab.Setup(this);
var settings = DirtWindowSettings.instance;
excluded = settings.IsExcluded(this);
if (excluded && !settings.visibility.HasFlag(VisibilityMask.Exclusions))
return;
var instanceTransform = instanceOwner.transform;
instance = new(instanceOwner, instanceTransform);
var target = instanceTransform;
if (prefab.targetPath != string.Empty)
target = target.Find(prefab.targetPath);
if (!target)
{
target = instanceTransform;
var indexed = prefab.targetIndexedPath.ToList();
while (target && indexed.Count > 0)
{
var i = indexed[0];
target = i < target.childCount ? target.GetChild(i) : null;
indexed.RemoveAt(0);
}
}
if (target)
{
if (prefab.GetTarget() is GameObject)
instance.SetTarget(target.gameObject);
else if (componentIndex > -1)
{
var components = target.GetComponents<Component>();
if (componentIndex < components.Length)
instance.SetTarget(components[componentIndex]);
}
}
instance.Setup(this);
}
public bool IsUnchangedButDirty()
{
if (appliedOrReverted)
return false;
if (prefab.isNothing && instance.isNothing)
return true;
if (!prefab.hasValue)
return false;
if (!instance.hasValue)
return false;
if (prefab.targetPropertyValue != instance.targetPropertyValue)
return false;
if (prefab.targetPropertyObjectValue != instance.targetPropertyObjectValue)
return false;
return true;
}
public void Apply()
{
appliedOrReverted = true;
PrefabUtility.ApplyPropertyOverride(
instance.GetTargetProperty(this),
AssetDatabase.GetAssetPath(prefab.GetOwner()),
InteractionMode.UserAction
);
}
public void Revert()
{
appliedOrReverted = true;
var p = instance.GetTargetProperty(this);
if (p is not null)
{
try
{
PrefabUtility.RevertPropertyOverride(p, InteractionMode.UserAction);
}
catch (System.ArgumentException ex)
{
Debug.LogWarning(ex);
}
return;
}
PrefabUtility.RemoveUnusedOverrides(
new[] { instance.GetOwner() },
InteractionMode.UserAction
);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bac8ea813b5bd654590e6733af511c58
@@ -0,0 +1,213 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace Dirt
{
public class ModificationReference
{
GameObject owner;
Object target;
public string targetPath { get; private set; }
public List<int> targetIndexedPath { get; private set; }
public string targetPropertyValue { get; private set; }
public bool hasTargetPropertyObjectValue { get; private set; }
public Object targetPropertyObjectValue { get; private set; }
public bool isNothing { get; private set; }
public bool hasValue { get; private set; }
public ModificationReference(GameObject owner, Object target)
{
SetOwner(owner);
SetTarget(target);
}
public void SetOwner(GameObject owner)
{
this.owner = owner;
}
public GameObject GetOwner()
{
if (!owner)
{
Debug.LogWarning("Reference has no owner");
}
return owner;
}
public List<Object> GetOwnerHierarchy()
{
var scenes = AssetDatabase.FindAssets("t:scene")
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<SceneAsset>)
.ToList();
var hierarchy = new List<Object>();
var current = GetOwner();
if (current)
{
var scene = current.scene;
hierarchy.Add(current);
while (current)
{
var parent = current.transform.parent;
if (parent)
{
current = parent.gameObject;
hierarchy.Add(current);
}
break;
}
if (scene.IsValid())
{
hierarchy.Add(scenes.FirstOrDefault(x => x.name == scene.name));
}
}
hierarchy.Reverse();
return hierarchy;
}
public void SetTarget(Object target)
{
this.target = target;
}
public Object GetTarget()
{
if (!target)
{
Debug.LogWarning($"{(owner ? $"{owner.name}: " : "")}Reference has no target");
}
return target;
}
public SerializedProperty GetTargetProperty(Modification modification)
{
var target = GetTarget();
if (!target)
{
return null;
}
var prefabObject = new SerializedObject(target);
return prefabObject.FindProperty(modification.propertyPath);
}
public int Setup(Modification modification)
{
var componentIndex = -1;
var owner = GetOwner();
var target = GetTarget();
if (owner && target)
{
var parent = target is Transform transform
? transform
: target is GameObject gameObject
? gameObject.transform
: (target as Component).transform;
var components = parent.GetComponents<Component>();
componentIndex = ArrayUtility.IndexOf(components, target);
var path = new List<string>();
targetIndexedPath = new List<int>();
while (parent && parent != owner.transform)
{
path.Insert(0, parent.name);
targetIndexedPath.Insert(0, parent.GetSiblingIndex());
parent = parent.parent;
}
targetPath = string.Join("/", path);
}
else
{
targetPath = string.Empty;
}
var p = GetTargetProperty(modification);
string v;
if (p is null)
{
isNothing = true;
v = "<color=#777777>(nothing)</color>";
}
else
{
hasValue = true;
switch (p.type)
{
case "bool":
v = $"<color=#9999ff>{p.boolValue}</color>";
break;
case "short":
case "int":
case "uint":
v = $"<color=#33ff33>{p.intValue}</color>";
break;
case "float":
v = $"<color=#99ff99>{p.floatValue:0.00}</color>";
break;
case "string":
v = p.stringValue == string.Empty
? $"<color=#cc9900>(empty)</color>"
: $"<color=#ffcc66>{p.stringValue}</color>";
break;
case "Enum":
if (0 <= p.enumValueIndex && p.enumValueIndex < p.enumNames.Length)
v = $"<color=#ffff00>{p.enumNames[p.enumValueIndex]}</color>";
else
v = $"<color=#ffff00>[type Enum] {p.enumValueIndex}</color>";
break;
case "ArraySize":
v = p.intValue.ToString();
break;
default:
if (Regex.IsMatch(p.type, @"^PPtr\<.+\>$"))
{
hasTargetPropertyObjectValue = true;
targetPropertyObjectValue = p.objectReferenceValue;
v = string.Empty;
break;
}
v = $"[type {p.type}]";
v = $"<color=#999999>{v}</color>";
hasValue = false;
break;
}
}
targetPropertyValue = v;
return componentIndex;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 006baea4fd256d548aab072abfa194a8
@@ -0,0 +1,12 @@
using System;
namespace Dirt
{
[Flags]
public enum VisibilityMask
{
ChangedDirtyValues = 1,
UnchangedDirtyValues = 2,
Exclusions = 4
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d6f1c54ec55190448b9feaebea2ee3e7