Add an internal class that tracks obj refs by path

This commit is contained in:
bd_ 2022-10-02 17:37:50 -07:00 committed by bd_
parent ea2011f68d
commit 74c725e987
7 changed files with 195 additions and 11 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f83eeca568d546a3b9a1d313e84b91cf
timeCreated: 1664754610

View File

@ -0,0 +1,121 @@
using UnityEditor;
using UnityEngine;
namespace net.fushizen.modular_avatar.core.editor
{
[CustomPropertyDrawer(typeof(AvatarObjectReference))]
public class AvatarObjectReferenceDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (!CustomGUI(position, property, label))
{
var xButtonSize = EditorStyles.miniButtonRight.CalcSize(new GUIContent("x"));
var xButtonRect = new Rect(position.xMax - xButtonSize.x, position.y, xButtonSize.x, position.height);
position = new Rect(position.x, position.y, position.width - xButtonSize.x, position.height);
var isNull = property.FindPropertyRelative(nameof(AvatarObjectReference.isNull));
property = property.FindPropertyRelative(nameof(AvatarObjectReference.referencePath));
position = EditorGUI.PrefixLabel(position, label);
EditorGUI.LabelField(position, isNull.boolValue ? "(null)" : property.stringValue);
}
}
private bool CustomGUI(Rect position, SerializedProperty property, GUIContent label)
{
var indentLevel = EditorGUI.indentLevel;
var color = GUI.contentColor;
var isNull = property.FindPropertyRelative(nameof(AvatarObjectReference.isNull));
property = property.FindPropertyRelative(nameof(AvatarObjectReference.referencePath));
try
{
// Find containing object, and from that the avatar
if (property.serializedObject == null || property.serializedObject.targetObjects.Length != 1)
return false;
var obj = property.serializedObject.targetObject as Component;
if (obj == null) return false;
var transform = obj.transform;
var avatar = RuntimeUtil.FindAvatarInParents(transform);
if (avatar == null) return false;
var target = isNull.boolValue ? null : avatar.transform.Find(property.stringValue);
var labelRect = position;
position = EditorGUI.PrefixLabel(position, label);
labelRect.width = position.x - labelRect.x;
var nullContent = GUIContent.none;
if (target != null || isNull.boolValue)
{
EditorGUI.BeginChangeCheck();
var newTarget = EditorGUI.ObjectField(position, nullContent, target, typeof(Transform), true);
if (EditorGUI.EndChangeCheck())
{
if (newTarget == null)
{
property.stringValue = "";
isNull.boolValue = true;
}
else
{
var relPath =
RuntimeUtil.RelativePath(avatar.gameObject, ((Transform) newTarget).gameObject);
if (relPath == null) return true;
property.stringValue = relPath;
isNull.boolValue = false;
}
}
}
else
{
// For some reason, this color change retroactively affects the prefix label above, so draw our own
// label as well (we still want the prefix label for highlights, etc).
EditorGUI.LabelField(labelRect, label);
GUI.contentColor = new Color(0, 0, 0, 0);
EditorGUI.BeginChangeCheck();
var newTarget = EditorGUI.ObjectField(position, nullContent, target, typeof(Transform), true);
GUI.contentColor = color;
if (EditorGUI.EndChangeCheck())
{
if (newTarget == null)
{
property.stringValue = "";
isNull.boolValue = true;
}
else
{
var relPath =
RuntimeUtil.RelativePath(avatar.gameObject, ((Transform) newTarget).gameObject);
if (relPath == null) return true;
property.stringValue = relPath;
isNull.boolValue = false;
}
}
else
{
GUI.contentColor = Color.red;
EditorGUI.LabelField(position, property.stringValue);
}
}
return true;
}
finally
{
GUI.contentColor = color;
EditorGUI.indentLevel = indentLevel;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2a52bc46649d4cf6863b147a830124c5
timeCreated: 1664754621

View File

@ -51,6 +51,8 @@ namespace net.fushizen.modular_avatar.core.editor
static Util() static Util()
{ {
RuntimeUtil.delayCall = (cb) => EditorApplication.delayCall += cb.Invoke; RuntimeUtil.delayCall = (cb) => EditorApplication.delayCall += cb.Invoke;
EditorApplication.hierarchyChanged += () => { RuntimeUtil.InvokeHierarchyChanged(); };
} }
public static AnimatorController CreateAnimator(AnimatorController toClone = null) public static AnimatorController CreateAnimator(AnimatorController toClone = null)

View File

@ -0,0 +1,44 @@
using System;
using UnityEngine;
namespace net.fushizen.modular_avatar.core
{
[Serializable]
public class AvatarObjectReference
{
public bool isNull = true;
public string referencePath = "";
private bool _cacheValid = false;
private string _cachedPath = "";
private GameObject _cachedReference;
public GameObject Get(Component container)
{
if (_cacheValid && _cachedPath == referencePath && !isNull) return _cachedReference;
_cacheValid = true;
_cachedPath = referencePath;
if (isNull)
{
_cachedReference = null;
return _cachedReference;
}
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;
RuntimeUtil.OnHierarchyChanged += InvalidateCache;
var avatar = RuntimeUtil.FindAvatarInParents(container.transform);
if (avatar == null) return (_cachedReference = null);
return (_cachedReference = avatar.transform.Find(referencePath)?.gameObject);
}
private void InvalidateCache()
{
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;
_cacheValid = false;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5a8276620b424854bdad151a08ebe5d0
timeCreated: 1664754280

View File

@ -24,7 +24,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
@ -37,6 +36,7 @@ namespace net.fushizen.modular_avatar.core
// Initialized in Util // Initialized in Util
public static Action<NullCallback> delayCall = (_) => { }; public static Action<NullCallback> delayCall = (_) => { };
public static event NullCallback OnHierarchyChanged;
public enum OnDemandSource public enum OnDemandSource
{ {
@ -52,16 +52,16 @@ namespace net.fushizen.modular_avatar.core
public static string RelativePath(GameObject root, GameObject child) public static string RelativePath(GameObject root, GameObject child)
{ {
if (root == child) return ""; if (root == child) return "";
List<string> pathSegments = new List<string>(); List<string> pathSegments = new List<string>();
while (child != root && child != null) while (child != root && child != null)
{ {
pathSegments.Add(child.name); pathSegments.Add(child.name);
child = child.transform.parent.gameObject; child = child.transform.parent?.gameObject;
} }
if (child == null) return null; if (child == null) return null;
pathSegments.Reverse(); pathSegments.Reverse();
return String.Join("/", pathSegments); return String.Join("/", pathSegments);
} }
@ -93,34 +93,38 @@ namespace net.fushizen.modular_avatar.core
{ {
UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(obj); UnityEditor.PrefabUtility.RecordPrefabInstancePropertyModifications(obj);
} }
UnityEditor.EditorUtility.SetDirty(obj); UnityEditor.EditorUtility.SetDirty(obj);
#endif #endif
} }
#if UNITY_EDITOR #if UNITY_EDITOR
private static UnityEngine.Object cachedAnimationWindowState; private static UnityEngine.Object cachedAnimationWindowState;
private static readonly Type animationWindowStateType
private static readonly Type animationWindowStateType
= typeof(UnityEditor.Editor).Assembly.GetType("UnityEditorInternal.AnimationWindowState"); = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditorInternal.AnimationWindowState");
private static readonly PropertyInfo recordingProp = animationWindowStateType.GetProperty( private static readonly PropertyInfo recordingProp = animationWindowStateType.GetProperty(
"recording", "recording",
BindingFlags.Instance | BindingFlags.Public BindingFlags.Instance | BindingFlags.Public
); );
private static readonly PropertyInfo previewingProp = animationWindowStateType.GetProperty( private static readonly PropertyInfo previewingProp = animationWindowStateType.GetProperty(
"previewing", "previewing",
BindingFlags.Instance | BindingFlags.Public BindingFlags.Instance | BindingFlags.Public
); );
private static readonly PropertyInfo playingProp = animationWindowStateType.GetProperty( private static readonly PropertyInfo playingProp = animationWindowStateType.GetProperty(
"playing", "playing",
BindingFlags.Instance | BindingFlags.Public BindingFlags.Instance | BindingFlags.Public
); );
#endif #endif
public static bool IsAnimationEditMode() public static bool IsAnimationEditMode()
{ {
#if !UNITY_EDITOR #if !UNITY_EDITOR
return false; return false;
#else #else
if (cachedAnimationWindowState == null) if (cachedAnimationWindowState == null)
{ {
foreach (var obj in Resources.FindObjectsOfTypeAll(animationWindowStateType)) foreach (var obj in Resources.FindObjectsOfTypeAll(animationWindowStateType))
@ -132,15 +136,19 @@ namespace net.fushizen.modular_avatar.core
if (cachedAnimationWindowState == null) return false; if (cachedAnimationWindowState == null) return false;
return (bool) recordingProp.GetValue(cachedAnimationWindowState, null) return (bool) recordingProp.GetValue(cachedAnimationWindowState, null)
|| (bool) previewingProp.GetValue(cachedAnimationWindowState, null) || (bool) previewingProp.GetValue(cachedAnimationWindowState, null)
|| (bool) playingProp.GetValue(cachedAnimationWindowState, null); || (bool) playingProp.GetValue(cachedAnimationWindowState, null);
#endif #endif
} }
#if UNITY_EDITOR #if UNITY_EDITOR
public static bool isPlaying => UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode; public static bool isPlaying => UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode;
#else #else
public static bool isPlaying => true; public static bool isPlaying => true;
#endif #endif
public static void InvokeHierarchyChanged()
{
OnHierarchyChanged?.Invoke();
}
} }
} }