From 9c86d7ded7b84ef47538d3327693e0dd5282b30b Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 12 Apr 2025 18:55:07 -0700 Subject: [PATCH] fix: play audio absolute paths in a relative controller break (#1555) In MA 1.11.x, while we corrected relative play audio paths where possible, we would leave them unchanged if no corresponding object was found. This meant that we maintained compatibility with older assets that uses absolute addressing for their Play Audio paths. This change restores that behavior. --- CHANGELOG-PRERELEASE-jp.md | 4 + CHANGELOG-PRERELEASE.md | 6 + CHANGELOG-jp.md | 4 + CHANGELOG.md | 6 + .../Animation/FixupAbsolutePlayAudioPass.cs | 46 ++++++++ .../FixupAbsolutePlayAudioPass.cs.meta | 3 + Editor/PluginDefinition/PluginDefinition.cs | 1 + Runtime/ModularAvatarMergeAnimator.cs | 8 +- .../PlayAudio/AbsoluteReference.controller | 103 ++++++++++++++++++ .../AbsoluteReference.controller.meta | 8 ++ .../Animation/PlayAudio/PlayAudioRemapping.cs | 5 + .../PlayAudio/PlayAudioRemapping.prefab | 23 ++++ 12 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 Editor/Animation/FixupAbsolutePlayAudioPass.cs create mode 100644 Editor/Animation/FixupAbsolutePlayAudioPass.cs.meta create mode 100644 UnitTests~/Animation/PlayAudio/AbsoluteReference.controller create mode 100644 UnitTests~/Animation/PlayAudio/AbsoluteReference.controller.meta diff --git a/CHANGELOG-PRERELEASE-jp.md b/CHANGELOG-PRERELEASE-jp.md index cd41bed8..caa5d7f4 100644 --- a/CHANGELOG-PRERELEASE-jp.md +++ b/CHANGELOG-PRERELEASE-jp.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1552] Merge Blend Treeにて、メインアバターFXレイヤーと同じ名前のintやboolパラメーターがBlend Treeに含まれている場合、 パラメーター型が修正されない問題を修正 - [#1553] リアクティブコンポーネントが生成するステートに、WD設定が正しくない問題を修正 +- [#1555] VRC Animator Play Audioが、Audio Sourceまでの絶対パスで設定されている場合に、相対パスのMerge Animator + コンポーネントとマージされた場合、指定されたオブジェクトが存在しないことを検出し、参照を絶対パスとして扱うように修正 + - 対象のパスにオブジェクトがある場合は、相対パスとして扱われます。安定性向上のためMerge Animatorコンポーネントと同じ +  指定方法を使用することをお勧めします。 ### Changed - [#1551] Merge Animatorは、遷移のない単一のstateを持つブレンドツリーのレイヤーに対して常にWDをONに設定します。 diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index eff57e12..46aadf29 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -25,6 +25,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1552] Merge Blend Tree failed to correct parameter types when the main avatar FX layer contained an int or bool parameter with the same name as one used in the blend tree. - [#1553] Reactive components might generate states with incorrect write default settings +- [#1555] Fixed compatibility regression from 1.11.x: VRC Animator Play Audio, when configured with an absolute path + but merged with a relative-path merge animator component, will now detect that the indicated object does not + exist, and treat the reference as an absolute path. + - Note that if there is an object in the target path, then it will be treated as a relative path. Using + addressing for Play Audio behaviors consistent with Merge Animator settings is therefore recommended as it will be + more robust. ### Changed - [#1551] Merge Animator will always set WD ON for single-state blendtree layers with no any state transitions. diff --git a/CHANGELOG-jp.md b/CHANGELOG-jp.md index c70772e2..139bc38b 100644 --- a/CHANGELOG-jp.md +++ b/CHANGELOG-jp.md @@ -11,6 +11,10 @@ Modular Avatarの主な変更点をこのファイルで記録しています。 ### Added ### Fixed +- [#1555] VRC Animator Play Audioが、Audio Sourceまでの絶対パスで設定されている場合に、相対パスのMerge Animator + コンポーネントとマージされた場合、指定されたオブジェクトが存在しないことを検出し、参照を絶対パスとして扱うように修正 + - 対象のパスにオブジェクトがある場合は、相対パスとして扱われます。安定性向上のためMerge Animatorコンポーネントと同じ +  指定方法を使用することをお勧めします。 ### Changed diff --git a/CHANGELOG.md b/CHANGELOG.md index 79800600..7ba0180a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Fixed +- [#1555] Fixed compatibility regression from 1.11.x: VRC Animator Play Audio, when configured with an absolute path + but merged with a relative-path merge animator component, will now detect that the indicated object does not + exist, and treat the reference as an absolute path. + - Note that if there is an object in the target path, then it will be treated as a relative path. Using + addressing for Play Audio behaviors consistent with Merge Animator settings is therefore recommended as it will be + more robust. ### Changed diff --git a/Editor/Animation/FixupAbsolutePlayAudioPass.cs b/Editor/Animation/FixupAbsolutePlayAudioPass.cs new file mode 100644 index 00000000..295ae7de --- /dev/null +++ b/Editor/Animation/FixupAbsolutePlayAudioPass.cs @@ -0,0 +1,46 @@ +#if MA_VRCSDK3_AVATARS + +using System.Linq; +using nadena.dev.modular_avatar.core; +using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; +using VRC.SDK3.Avatars.Components; + +namespace nadena.dev.modular_avatar.animation +{ + [RunsOnPlatforms(WellKnownPlatforms.VRChatAvatar30)] + public class FixupAbsolutePlayAudioPass : Pass + { + protected override void Execute(BuildContext context) + { + // Older versions of modular avatar did not adjust Animator Play Audio paths when they were absolute paths. + // Replicate this behavior here. + + // Note that this runs before any object movement. + + var asc = context.Extension(); + + foreach (var mama in context.AvatarRootTransform.GetComponentsInChildren(true)) + { + if (!mama._wasRelative) continue; + + var pathPrefix = asc.ObjectPathRemapper.GetVirtualPathForObject(mama.gameObject) + "/"; + + foreach (var state in asc.ControllerContext.Controllers[mama].AllReachableNodes() + .OfType()) + { + foreach (var behavior in state.Behaviours.OfType()) + { + if (asc.ObjectPathRemapper.GetObjectForPath(behavior.SourcePath) != null) continue; + if (behavior.SourcePath.StartsWith(pathPrefix)) + { + behavior.SourcePath = behavior.SourcePath.Substring(pathPrefix.Length); + } + } + } + } + } + } +} + +#endif \ No newline at end of file diff --git a/Editor/Animation/FixupAbsolutePlayAudioPass.cs.meta b/Editor/Animation/FixupAbsolutePlayAudioPass.cs.meta new file mode 100644 index 00000000..5701c80d --- /dev/null +++ b/Editor/Animation/FixupAbsolutePlayAudioPass.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a763cbc4bac94063b6b084ea3f4d8206 +timeCreated: 1744422528 \ No newline at end of file diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 54f9f093..30e832da 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -61,6 +61,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 => { #if MA_VRCSDK3_AVATARS + seq.Run(FixupAbsolutePlayAudioPass.Instance); seq.Run(MMDRelayEarlyPass.Instance); seq.Run(RenameParametersPluginPass.Instance); seq.Run(ParameterAssignerPass.Instance); diff --git a/Runtime/ModularAvatarMergeAnimator.cs b/Runtime/ModularAvatarMergeAnimator.cs index 5ff78e83..33782f49 100644 --- a/Runtime/ModularAvatarMergeAnimator.cs +++ b/Runtime/ModularAvatarMergeAnimator.cs @@ -58,6 +58,8 @@ namespace nadena.dev.modular_avatar.core public AvatarObjectReference relativePathRoot = new AvatarObjectReference(); public int layerPriority = 0; public MergeAnimatorMode mergeAnimatorMode = MergeAnimatorMode.Append; + + internal bool _wasRelative; public override void ResolveReferences() { @@ -88,7 +90,11 @@ namespace nadena.dev.modular_avatar.core string IVirtualizeAnimatorController.GetMotionBasePath(object ndmfBuildContext, bool clearPath) { var path = GetMotionBasePathCallback(this, ndmfBuildContext); - if (clearPath) pathMode = MergeAnimatorPathMode.Absolute; + if (clearPath) + { + _wasRelative = _wasRelative || pathMode == MergeAnimatorPathMode.Relative; + pathMode = MergeAnimatorPathMode.Absolute; + } return path; } diff --git a/UnitTests~/Animation/PlayAudio/AbsoluteReference.controller b/UnitTests~/Animation/PlayAudio/AbsoluteReference.controller new file mode 100644 index 00000000..5f9ffeab --- /dev/null +++ b/UnitTests~/Animation/PlayAudio/AbsoluteReference.controller @@ -0,0 +1,103 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1107 &-6982002469074649382 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: absolute + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 5394444500086494115} + m_Position: {x: 330, y: 60, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 5394444500086494115} +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AbsoluteReference + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: absolute + m_StateMachine: {fileID: -6982002469074649382} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!114 &2414404072947734855 +MonoBehaviour: + m_ObjectHideFlags: 1 + 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: 1859411423, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Source: {fileID: 0} + SourcePath: Bone Proxy/Audio Source + PlaybackOrder: 0 + ParameterName: + Volume: {x: 1, y: 1} + VolumeApplySettings: 1 + Pitch: {x: 1, y: 1} + PitchApplySettings: 1 + Clips: [] + ClipsApplySettings: 1 + Loop: 0 + LoopApplySettings: 1 + DelayInSeconds: 0 + PlayOnEnter: 1 + StopOnEnter: 1 + PlayOnExit: 0 + StopOnExit: 0 + playbackIndex: 0 +--- !u!1102 &5394444500086494115 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New State + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: + - {fileID: 2414404072947734855} + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: diff --git a/UnitTests~/Animation/PlayAudio/AbsoluteReference.controller.meta b/UnitTests~/Animation/PlayAudio/AbsoluteReference.controller.meta new file mode 100644 index 00000000..f85acf47 --- /dev/null +++ b/UnitTests~/Animation/PlayAudio/AbsoluteReference.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 025d88351bb99eb4f8b38dbe071eaf2a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.cs b/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.cs index 8bdf1489..c5a9fa15 100644 --- a/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.cs +++ b/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.cs @@ -26,6 +26,11 @@ namespace modular_avatar_tests var subState = layer.stateMachine.stateMachines[0].stateMachine.states[0].state; var playAudio2 = (VRCAnimatorPlayAudio) subState.behaviours[0]; Assert.AreEqual("New Parent/Bone Proxy/Audio Source", playAudio2.SourcePath); + + var absLayer = findFxLayer(prefab, "absolute"); + state = absLayer.stateMachine.states[0].state; + playAudio = (VRCAnimatorPlayAudio) state.behaviours[0]; + Assert.AreEqual("New Parent/Bone Proxy/Audio Source", playAudio.SourcePath); } } } diff --git a/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.prefab b/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.prefab index c371b7c3..ea1b9b68 100644 --- a/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.prefab +++ b/UnitTests~/Animation/PlayAudio/PlayAudioRemapping.prefab @@ -335,6 +335,7 @@ GameObject: m_Component: - component: {fileID: 2374223349072045661} - component: {fileID: 2173147159387031478} + - component: {fileID: 4651177154340700720} m_Layer: 0 m_Name: Bone Proxy m_TagString: Untagged @@ -373,6 +374,28 @@ MonoBehaviour: boneReference: 55 subPath: New Parent attachmentMode: 1 +--- !u!114 &4651177154340700720 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6739982813768973813} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 025d88351bb99eb4f8b38dbe071eaf2a, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 + mergeAnimatorMode: 0 --- !u!1 &7552762365415873619 GameObject: m_ObjectHideFlags: 0