From 4c44c576defc815ad998f5d56d4d947845c74204 Mon Sep 17 00:00:00 2001 From: bd_ Date: Tue, 11 Mar 2025 19:21:21 -0700 Subject: [PATCH] fix: Merge Animator in replace mode breaks Merge Blend Tree (#1488) --- Editor/MergeAnimatorProcessor.cs | 3 +- .../MergeAnimatorReplacementTest.cs | 14 + .../Replacement/New BlendTree.asset | 17 + .../Replacement/New BlendTree.asset.meta | 8 + .../ReplacePreservesMergedBlendTrees.prefab | 429 ++++++++++++++++++ ...placePreservesMergedBlendTrees.prefab.meta | 7 + 6 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset create mode 100644 UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset.meta create mode 100644 UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab create mode 100644 UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab.meta diff --git a/Editor/MergeAnimatorProcessor.cs b/Editor/MergeAnimatorProcessor.cs index fbfbb487..b331ee59 100644 --- a/Editor/MergeAnimatorProcessor.cs +++ b/Editor/MergeAnimatorProcessor.cs @@ -104,7 +104,8 @@ namespace nadena.dev.modular_avatar.core.editor else if (replacements == 1) { // Delete all pre-existing layers. - controller.RemoveLayers(_ => true); + // Retain the blend tree layer, since that will generally be placed as the first layer in the animator + controller.RemoveLayers(l => l.Name != MergeBlendTreePass.BlendTreeLayerName); // Merge just the first controller (the one that replaces) MergeSingle(context, controller, sorted.First(), null); diff --git a/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs index 45d02d16..b96368a0 100644 --- a/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs +++ b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs @@ -1,6 +1,7 @@ using System.Linq; using modular_avatar_tests; using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; using NUnit.Framework; using UnityEditor.Animations; @@ -54,5 +55,18 @@ namespace UnitTests.MergeAnimatorTests.Replacement Assert.AreEqual("error.merge_animator.multiple_replacements", err.TitleKey); Assert.AreEqual(2, err._references.Count()); } + + [Test] + public void ReplacementPreservesMergedBlendTrees() + { + var prefab = CreatePrefab("ReplacePreservesMergedBlendTrees.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + Assert.IsTrue(fxc.layers.Any(l => l.name == MergeBlendTreePass.BlendTreeLayerName)); + } } } \ No newline at end of file diff --git a/UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset b/UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset new file mode 100644 index 00000000..9af142cc --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!206 &20600000 +BlendTree: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New BlendTree + m_Childs: [] + m_BlendParameter: Blend + m_BlendParameterY: Blend + m_MinThreshold: 0 + m_MaxThreshold: 1 + m_UseAutomaticThresholds: 1 + m_NormalizedBlendValues: 0 + m_BlendType: 0 diff --git a/UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset.meta b/UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset.meta new file mode 100644 index 00000000..d4e10746 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/New BlendTree.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a1844e7b62a8a51469ced7147dd44aa7 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 20600000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab b/UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab new file mode 100644 index 00000000..36d9f717 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab @@ -0,0 +1,429 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2475619896218027151 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6365181863859417131} + - component: {fileID: 3305707577319064009} + m_Layer: 0 + m_Name: replace animator + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6365181863859417131 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2475619896218027151} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8605830441483851176} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3305707577319064009 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2475619896218027151} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: d68bd7182dbae1d49a1d2f642d5a7af0, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 + mergeAnimatorMode: 1 +--- !u!1 &3291952896771917734 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 276835204528809388} + - component: {fileID: 889066846472530445} + m_Layer: 0 + m_Name: merge dbt + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &276835204528809388 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3291952896771917734} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8605830441483851176} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &889066846472530445 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3291952896771917734} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 229dd561ca024a6588e388160921a70f, type: 3} + m_Name: + m_EditorClassIdentifier: + BlendTree: {fileID: 20600000, guid: a1844e7b62a8a51469ced7147dd44aa7, type: 2} + PathMode: 0 + RelativePathRoot: + referencePath: + targetObject: {fileID: 0} +--- !u!1 &9064781923578636319 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8605830441483851176} + - component: {fileID: 4913287206899290532} + - component: {fileID: 5887473339311439940} + - component: {fileID: 3327905988599616909} + m_Layer: 0 + m_Name: ReplacePreservesMergedBlendTrees + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8605830441483851176 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9064781923578636319} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.2262063, y: 0.33258107, z: -0.3286315} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 276835204528809388} + - {fileID: 6365181863859417131} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &4913287206899290532 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9064781923578636319} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &5887473339311439940 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9064781923578636319} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Name: + ViewPosition: {x: 0, y: 1.6, z: 0.2} + Animations: 0 + ScaleIPD: 1 + lipSync: 0 + lipSyncJawBone: {fileID: 0} + lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1} + lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1} + VisemeSkinnedMesh: {fileID: 0} + MouthOpenBlendShapeName: Facial_Blends.Jaw_Down + VisemeBlendShapes: [] + unityVersion: + portraitCameraPositionOffset: {x: 0, y: 0, z: 0} + portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + networkIDs: [] + customExpressions: 0 + expressionsMenu: {fileID: 0} + expressionParameters: {fileID: 0} + enableEyeLook: 0 + customEyeLookSettings: + eyeMovement: + confidence: 0.5 + excitement: 0.5 + leftEye: {fileID: 0} + rightEye: {fileID: 0} + eyesLookingStraight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingUp: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingDown: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingLeft: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingRight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidType: 0 + upperLeftEyelid: {fileID: 0} + upperRightEyelid: {fileID: 0} + lowerLeftEyelid: {fileID: 0} + lowerRightEyelid: {fileID: 0} + eyelidsDefault: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsClosed: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingUp: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingDown: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsSkinnedMesh: {fileID: 0} + eyelidsBlendshapes: + customizeAnimationLayers: 0 + baseAnimationLayers: + - isEnabled: 0 + type: 0 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 4 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 5 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + specialAnimationLayers: + - isEnabled: 0 + type: 6 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 7 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 8 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + AnimationPreset: {fileID: 0} + animationHashSet: [] + autoFootsteps: 1 + autoLocomotion: 1 + collider_head: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_torso: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} +--- !u!114 &3327905988599616909 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9064781923578636319} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_Name: + m_EditorClassIdentifier: + launchedFromSDKPipeline: 0 + completedSDKPipeline: 0 + blueprintId: + contentType: 0 + assetBundleUnityVersion: + fallbackStatus: 0 diff --git a/UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab.meta b/UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab.meta new file mode 100644 index 00000000..ea2ae386 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/ReplacePreservesMergedBlendTrees.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f84a54a394e3f7447ae28c87e5c17698 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: