feat: update AvatarObjectReference paths when target object is moved in scene (#1074)

Closes: #1037
This commit is contained in:
bd_ 2024-09-01 17:29:58 -07:00 committed by GitHub
parent 682a0de0e0
commit 0a6270bb43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 159 additions and 11 deletions

View File

@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf.preview;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
public static class ObjectReferenceFixer
{
private static ComputeContext _context;
private static PrefabStage _lastStage;
[InitializeOnLoadMethod]
private static void Init()
{
EditorApplication.delayCall += ProcessObjectReferences;
EditorApplication.update += () =>
{
if (PrefabStageUtility.GetCurrentPrefabStage() != _lastStage) _context?.Invalidate?.Invoke();
};
}
private static void ProcessObjectReferences()
{
_lastStage = PrefabStageUtility.GetCurrentPrefabStage();
_context = new ComputeContext("ObjectReferenceFixer");
_context.InvokeOnInvalidate<object>(typeof(ObjectReferenceFixer), _ => ProcessObjectReferences());
IEnumerable<IHaveObjReferences> withReferences = _context.GetComponentsByType<IHaveObjReferences>();
if (_lastStage != null)
withReferences =
withReferences.Concat(
_context.GetComponentsInChildren<IHaveObjReferences>(_lastStage.prefabContentsRoot, true)
);
foreach (var obj in withReferences)
{
var component = obj as Component;
if (component == null) continue;
var avatar = _context.GetAvatarRoot(component.gameObject);
if (avatar == null) continue;
var references = _context.Observe(component,
c => ((IHaveObjReferences)c).GetObjectReferences().Select(
r => (r.targetObject, r.referencePath, r)
),
Enumerable.SequenceEqual
);
var dirty = false;
foreach (var (targetObject, referencePath, objRef) in references)
{
if (targetObject == null) continue;
_context.ObservePath(targetObject.transform);
if (!targetObject.transform.IsChildOf(avatar.transform)) continue;
if (objRef.IsConsistent(avatar)) continue;
if (!dirty)
{
dirty = true;
Undo.RecordObject(component, "");
}
objRef.Set(targetObject);
}
if (dirty)
{
EditorUtility.SetDirty(component);
PrefabUtility.RecordPrefabInstancePropertyModifications(component);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eba8a4ec992b42d894f9206c642c49ad
timeCreated: 1725229885

View File

@ -1,8 +1,8 @@
using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
@ -124,6 +124,12 @@ namespace nadena.dev.modular_avatar.core
targetObject = target;
}
internal bool IsConsistent(GameObject avatarRoot)
{
if (referencePath == AVATAR_ROOT) return targetObject == avatarRoot;
return avatarRoot.transform.Find(referencePath)?.gameObject == targetObject;
}
private void InvalidateCache()
{
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;

View File

@ -24,7 +24,6 @@
using System;
using UnityEngine;
#if MA_VRCSDK3_AVATARS
using VRC.SDKBase;
#endif

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace nadena.dev.modular_avatar.core
{
internal interface IHaveObjReferences
{
IEnumerable<AvatarObjectReference> GetObjectReferences();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 979eb029e78d4916a24742853e8d7e53
timeCreated: 1725229779

View File

@ -36,7 +36,7 @@ namespace nadena.dev.modular_avatar.core
[ExecuteAlways]
[AddComponentMenu("Modular Avatar/MA Blendshape Sync")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/blendshape-sync?lang=auto")]
public class ModularAvatarBlendshapeSync : AvatarTagComponent
public class ModularAvatarBlendshapeSync : AvatarTagComponent, IHaveObjReferences
{
public List<BlendshapeBinding> Bindings = new List<BlendshapeBinding>();
@ -126,5 +126,12 @@ namespace nadena.dev.modular_avatar.core
localRenderer.SetBlendShapeWeight(binding.LocalBlendshapeIndex, weight);
}
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
foreach (var binding in Bindings)
if (binding.ReferenceMesh != null)
yield return binding.ReferenceMesh;
}
}
}

View File

@ -47,7 +47,7 @@ namespace nadena.dev.modular_avatar.core
[DisallowMultipleComponent]
[AddComponentMenu("Modular Avatar/MA Merge Armature")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-armature?lang=auto")]
public class ModularAvatarMergeArmature : AvatarTagComponent
public class ModularAvatarMergeArmature : AvatarTagComponent, IHaveObjReferences
{
public AvatarObjectReference mergeTarget = new AvatarObjectReference();
public GameObject mergeTargetObject => mergeTarget.Get(this);
@ -236,5 +236,10 @@ namespace nadena.dev.modular_avatar.core
RuntimeUtil.MarkDirty(this);
}
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
if (mergeTarget != null) yield return mergeTarget;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace nadena.dev.modular_avatar.core
@ -6,7 +7,7 @@ namespace nadena.dev.modular_avatar.core
[AddComponentMenu("Modular Avatar/MA Mesh Settings")]
[DisallowMultipleComponent]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/mesh-settings?lang=auto")]
public class ModularAvatarMeshSettings : AvatarTagComponent
public class ModularAvatarMeshSettings : AvatarTagComponent, IHaveObjReferences
{
internal static readonly Bounds DEFAULT_BOUNDS = new Bounds(Vector3.zero, Vector3.one * 2);
@ -34,5 +35,11 @@ namespace nadena.dev.modular_avatar.core
ProbeAnchor?.Get(this);
RootBone?.Get(this);
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
if (ProbeAnchor != null) yield return ProbeAnchor;
if (RootBone != null) yield return RootBone;
}
}
}

View File

@ -1,11 +1,12 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
[AddComponentMenu("Modular Avatar/MA Replace Object")]
[DisallowMultipleComponent]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/replace-object?lang=auto")]
public class ModularAvatarReplaceObject : AvatarTagComponent
public class ModularAvatarReplaceObject : AvatarTagComponent, IHaveObjReferences
{
public AvatarObjectReference targetObject = new AvatarObjectReference();
@ -13,5 +14,10 @@ namespace nadena.dev.modular_avatar.core
{
targetObject?.Get(this);
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
if (targetObject != null) yield return targetObject;
}
}
}

View File

@ -39,8 +39,7 @@ namespace nadena.dev.modular_avatar.core
[AddComponentMenu("Modular Avatar/MA Material Setter")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/material-setter?lang=auto")]
public class ModularAvatarMaterialSetter : ReactiveComponent
public class ModularAvatarMaterialSetter : ReactiveComponent, IHaveObjReferences
{
[SerializeField] private List<MaterialSwitchObject> m_objects = new();
@ -57,5 +56,12 @@ namespace nadena.dev.modular_avatar.core
obj.Object?.Get(this);
}
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
foreach (var obj in m_objects)
if (obj.Object != null)
yield return obj.Object;
}
}
}

View File

@ -22,7 +22,7 @@ namespace nadena.dev.modular_avatar.core
[AddComponentMenu("Modular Avatar/MA Object Toggle")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/object-toggle?lang=auto")]
public class ModularAvatarObjectToggle : ReactiveComponent
public class ModularAvatarObjectToggle : ReactiveComponent, IHaveObjReferences
{
[SerializeField] private List<ToggledObject> m_objects = new();
@ -39,5 +39,12 @@ namespace nadena.dev.modular_avatar.core
obj.Object?.Get(this);
}
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
foreach (var obj in m_objects)
if (obj.Object != null)
yield return obj.Object;
}
}
}

View File

@ -58,7 +58,7 @@ namespace nadena.dev.modular_avatar.core
[AddComponentMenu("Modular Avatar/MA Shape Changer")]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/shape-changer?lang=auto")]
public class ModularAvatarShapeChanger : ReactiveComponent
public class ModularAvatarShapeChanger : ReactiveComponent, IHaveObjReferences
{
// Migration field to help with 1.10-beta series avatar data. Since this was never in a released version of MA,
// this migration support will be removed in 1.10.0.
@ -115,5 +115,12 @@ namespace nadena.dev.modular_avatar.core
m_targetRenderer.targetObject = null;
}
}
public IEnumerable<AvatarObjectReference> GetObjectReferences()
{
foreach (var shape in m_shapes)
if (shape.Object != null)
yield return shape.Object;
}
}
}