diff --git a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs index 8fd8031a..0c7cf875 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/BoneProxyProcessor.cs @@ -22,9 +22,7 @@ * SOFTWARE. */ -using UnityEditor; using UnityEngine; -using VRC.SDKBase.Editor.BuildPipeline; namespace net.fushizen.modular_avatar.core.editor { @@ -36,10 +34,6 @@ namespace net.fushizen.modular_avatar.core.editor foreach (var proxy in boneProxies) { - if (proxy.constraint != null && proxy.constraint.gameObject == proxy.gameObject) - { - UnityEngine.Object.DestroyImmediate(proxy.constraint); - } if (proxy.target != null) { var oldPath = RuntimeUtil.AvatarRootPath(proxy.gameObject); @@ -47,11 +41,13 @@ namespace net.fushizen.modular_avatar.core.editor transform.SetParent(proxy.target, false); transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; - PathMappings.Remap(oldPath, new PathMappings.MappingEntry() { + PathMappings.Remap(oldPath, new PathMappings.MappingEntry() + { path = RuntimeUtil.AvatarRootPath(proxy.gameObject), transformPath = RuntimeUtil.AvatarRootPath(proxy.gameObject) }); } + Object.DestroyImmediate(proxy); } } diff --git a/Packages/net.fushizen.modular-avatar/Editor/Inspector/BoneProxyEditor.cs b/Packages/net.fushizen.modular-avatar/Editor/Inspector/BoneProxyEditor.cs new file mode 100644 index 00000000..876fe1a4 --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Editor/Inspector/BoneProxyEditor.cs @@ -0,0 +1,74 @@ +using UnityEditor; +using UnityEngine; + +namespace net.fushizen.modular_avatar.core.editor +{ + internal class TempObjRef : ScriptableObject + { + public Transform target; + } + + [CustomEditor(typeof(ModularAvatarBoneProxy))] + [CanEditMultipleObjects] + public class BoneProxyEditor : Editor + { + private bool foldout = false; + + private Object[] objRefs; + + private void OnEnable() + { + objRefs = new Object[targets.Length]; + for (int i = 0; i < targets.Length; i++) + { + objRefs[i] = ScriptableObject.CreateInstance(); + } + } + + public override void OnInspectorGUI() + { + GameObject parentAvatar = null; + + for (int i = 0; i < targets.Length; i++) + { + var t = (ModularAvatarBoneProxy) targets[i]; + var av = RuntimeUtil.FindAvatarInParents(t.transform); + + if (parentAvatar == null) parentAvatar = av.gameObject; + if (av == null || parentAvatar != av.gameObject) + { + base.OnInspectorGUI(); + return; + } + + ((TempObjRef) objRefs[i]).target = t.target; + } + + var virtObj = new SerializedObject(objRefs); + var virtProp = virtObj.FindProperty(nameof(TempObjRef.target)); + + var currentTarget = targets.Length != 1 ? null : ((ModularAvatarBoneProxy) targets[0]).target; + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.PropertyField(virtProp); + if (EditorGUI.EndChangeCheck()) + { + virtObj.ApplyModifiedPropertiesWithoutUndo(); + for (int i = 0; i < targets.Length; i++) + { + var t = (ModularAvatarBoneProxy) targets[i]; + Undo.RecordObjects(targets, "Set targets"); + t.target = ((TempObjRef) objRefs[i]).target; + } + } + + foldout = EditorGUILayout.Foldout(foldout, "Advanced"); + if (foldout) + { + EditorGUI.indentLevel++; + base.OnInspectorGUI(); + EditorGUI.indentLevel--; + } + } + } +} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/Inspector/BoneProxyEditor.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/Inspector/BoneProxyEditor.cs.meta new file mode 100644 index 00000000..9aceb178 --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Editor/Inspector/BoneProxyEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fc00efdbac944f7886df9bd83edffe5b +timeCreated: 1664757842 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs index a2625d16..ca7ca00d 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarBoneProxy.cs @@ -25,60 +25,51 @@ using System; using System.Collections.Generic; using UnityEngine; -using UnityEngine.Animations; namespace net.fushizen.modular_avatar.core { [ExecuteInEditMode] public class ModularAvatarBoneProxy : AvatarTagComponent { - public Transform target; + private Transform _targetCache; - public HumanBodyBones boneReference = HumanBodyBones.LastBone; - public string subPath; - - [SerializeField] [HideInInspector] public ParentConstraint constraint; - - - void OnValidate() + public Transform target { -#if UNITY_EDITOR - UnityEditor.EditorApplication.delayCall += CheckReferences; -#endif - } - - void CheckReferences() - { - if (this == null) return; // post-destroy - - if (target == null && (boneReference != HumanBodyBones.LastBone || !string.IsNullOrWhiteSpace(subPath))) + get { + if (_targetCache != null) return _targetCache; UpdateDynamicMapping(); - if (target != null) - { - RuntimeUtil.MarkDirty(this); - } + RuntimeUtil.OnHierarchyChanged -= ClearCache; + RuntimeUtil.OnHierarchyChanged += ClearCache; + return _targetCache; } - else if (target != null) + set { var origBoneReference = boneReference; var origSubpath = subPath; - UpdateStaticMapping(); + UpdateStaticMapping(value); if (origSubpath != subPath || origBoneReference != boneReference) { RuntimeUtil.MarkDirty(this); } - } - CheckConstraint(); + RuntimeUtil.OnHierarchyChanged -= ClearCache; + RuntimeUtil.OnHierarchyChanged += ClearCache; + } } - private void CheckConstraint() + public HumanBodyBones boneReference = HumanBodyBones.LastBone; + public string subPath; + + void OnValidate() { - if (constraint != null) - { - DestroyImmediate(constraint, true); - } + ClearCache(); + } + + void ClearCache() + { + _targetCache = null; + RuntimeUtil.OnHierarchyChanged -= ClearCache; } private void Update() @@ -94,16 +85,16 @@ namespace net.fushizen.modular_avatar.core private void OnDestroy() { -#if UNITY_EDITOR - UnityEditor.EditorApplication.delayCall += () => - { - if (constraint != null) DestroyImmediate(constraint); - }; -#endif + RuntimeUtil.OnHierarchyChanged -= ClearCache; } private void UpdateDynamicMapping() { + if (boneReference == HumanBodyBones.LastBone) + { + return; + } + var avatar = RuntimeUtil.FindAvatarInParents(transform); if (avatar == null) return; @@ -123,11 +114,11 @@ namespace net.fushizen.modular_avatar.core if (animator == null) return; var bone = animator.GetBoneTransform(boneReference); if (bone == null) return; - if (string.IsNullOrWhiteSpace(subPath)) target = bone; - else target = bone.Find(subPath); + if (string.IsNullOrWhiteSpace(subPath)) _targetCache = bone; + else _targetCache = bone.Find(subPath); } - private void UpdateStaticMapping() + private void UpdateStaticMapping(Transform newTarget) { var avatar = RuntimeUtil.FindAvatarInParents(transform); var humanBones = new Dictionary(); @@ -145,10 +136,10 @@ namespace net.fushizen.modular_avatar.core if (bone != null) humanBones[bone] = boneType; } - Transform iter = target; + Transform iter = newTarget; Transform avatarTransform = avatar.transform; - if (target == avatarTransform) + if (newTarget == avatarTransform) { boneReference = HumanBodyBones.LastBone; subPath = "$$AVATAR"; @@ -169,7 +160,8 @@ namespace net.fushizen.modular_avatar.core boneReference = humanBones[iter]; } - subPath = RuntimeUtil.RelativePath(iter.gameObject, target.gameObject); + subPath = RuntimeUtil.RelativePath(iter.gameObject, newTarget.gameObject); + _targetCache = newTarget; } } } \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs b/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs index a9f76878..ae60f04d 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/RuntimeUtil.cs @@ -27,6 +27,9 @@ using System.Collections.Generic; using JetBrains.Annotations; using UnityEngine; using VRC.SDK3.Avatars.Components; +#if UNITY_EDITOR +using System.Reflection; +#endif namespace net.fushizen.modular_avatar.core {