diff --git a/Editor/ObjectReferenceFixer.cs b/Editor/ObjectReferenceFixer.cs new file mode 100644 index 00000000..b6fe69e6 --- /dev/null +++ b/Editor/ObjectReferenceFixer.cs @@ -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(typeof(ObjectReferenceFixer), _ => ProcessObjectReferences()); + + IEnumerable withReferences = _context.GetComponentsByType(); + if (_lastStage != null) + withReferences = + withReferences.Concat( + _context.GetComponentsInChildren(_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); + } + } + } + } +} \ No newline at end of file diff --git a/Editor/ObjectReferenceFixer.cs.meta b/Editor/ObjectReferenceFixer.cs.meta new file mode 100644 index 00000000..748f5385 --- /dev/null +++ b/Editor/ObjectReferenceFixer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: eba8a4ec992b42d894f9206c642c49ad +timeCreated: 1725229885 \ No newline at end of file diff --git a/Runtime/AvatarObjectReference.cs b/Runtime/AvatarObjectReference.cs index be043c00..9e98e72e 100644 --- a/Runtime/AvatarObjectReference.cs +++ b/Runtime/AvatarObjectReference.cs @@ -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; diff --git a/Runtime/AvatarTagComponent.cs b/Runtime/AvatarTagComponent.cs index fd5ef258..bb9d73f9 100644 --- a/Runtime/AvatarTagComponent.cs +++ b/Runtime/AvatarTagComponent.cs @@ -24,7 +24,6 @@ using System; using UnityEngine; - #if MA_VRCSDK3_AVATARS using VRC.SDKBase; #endif diff --git a/Runtime/IHaveObjReferences.cs b/Runtime/IHaveObjReferences.cs new file mode 100644 index 00000000..d1ee015f --- /dev/null +++ b/Runtime/IHaveObjReferences.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace nadena.dev.modular_avatar.core +{ + internal interface IHaveObjReferences + { + IEnumerable GetObjectReferences(); + } +} \ No newline at end of file diff --git a/Runtime/IHaveObjReferences.cs.meta b/Runtime/IHaveObjReferences.cs.meta new file mode 100644 index 00000000..47b1be7a --- /dev/null +++ b/Runtime/IHaveObjReferences.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 979eb029e78d4916a24742853e8d7e53 +timeCreated: 1725229779 \ No newline at end of file diff --git a/Runtime/ModularAvatarBlendshapeSync.cs b/Runtime/ModularAvatarBlendshapeSync.cs index 40088e18..cbaef34f 100644 --- a/Runtime/ModularAvatarBlendshapeSync.cs +++ b/Runtime/ModularAvatarBlendshapeSync.cs @@ -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 Bindings = new List(); @@ -126,5 +126,12 @@ namespace nadena.dev.modular_avatar.core localRenderer.SetBlendShapeWeight(binding.LocalBlendshapeIndex, weight); } } + + public IEnumerable GetObjectReferences() + { + foreach (var binding in Bindings) + if (binding.ReferenceMesh != null) + yield return binding.ReferenceMesh; + } } } \ No newline at end of file diff --git a/Runtime/ModularAvatarMergeArmature.cs b/Runtime/ModularAvatarMergeArmature.cs index 489130f3..36e5e36e 100644 --- a/Runtime/ModularAvatarMergeArmature.cs +++ b/Runtime/ModularAvatarMergeArmature.cs @@ -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 GetObjectReferences() + { + if (mergeTarget != null) yield return mergeTarget; + } } } \ No newline at end of file diff --git a/Runtime/ModularAvatarMeshSettings.cs b/Runtime/ModularAvatarMeshSettings.cs index fc9b870d..339cb782 100644 --- a/Runtime/ModularAvatarMeshSettings.cs +++ b/Runtime/ModularAvatarMeshSettings.cs @@ -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 GetObjectReferences() + { + if (ProbeAnchor != null) yield return ProbeAnchor; + if (RootBone != null) yield return RootBone; + } } } \ No newline at end of file diff --git a/Runtime/ModularAvatarReplaceObject.cs b/Runtime/ModularAvatarReplaceObject.cs index ab2002af..b00c6ad8 100644 --- a/Runtime/ModularAvatarReplaceObject.cs +++ b/Runtime/ModularAvatarReplaceObject.cs @@ -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 GetObjectReferences() + { + if (targetObject != null) yield return targetObject; + } } } \ No newline at end of file diff --git a/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs b/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs index 28f4f9c0..0a919dd0 100644 --- a/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs +++ b/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs @@ -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 m_objects = new(); @@ -57,5 +56,12 @@ namespace nadena.dev.modular_avatar.core obj.Object?.Get(this); } } + + public IEnumerable GetObjectReferences() + { + foreach (var obj in m_objects) + if (obj.Object != null) + yield return obj.Object; + } } } \ No newline at end of file diff --git a/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs b/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs index a105ed63..93b1421a 100644 --- a/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs +++ b/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs @@ -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 m_objects = new(); @@ -39,5 +39,12 @@ namespace nadena.dev.modular_avatar.core obj.Object?.Get(this); } } + + public IEnumerable GetObjectReferences() + { + foreach (var obj in m_objects) + if (obj.Object != null) + yield return obj.Object; + } } } \ No newline at end of file diff --git a/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs b/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs index 21a17fab..d8e1dea5 100644 --- a/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs +++ b/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs @@ -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 GetObjectReferences() + { + foreach (var shape in m_shapes) + if (shape.Object != null) + yield return shape.Object; + } } } \ No newline at end of file