diff --git a/CHANGELOG-PRERELEASE-jp.md b/CHANGELOG-PRERELEASE-jp.md index 4150c88e..b06cc608 100644 --- a/CHANGELOG-PRERELEASE-jp.md +++ b/CHANGELOG-PRERELEASE-jp.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- [#1482] `Merge Animator` に既存のアニメーターコントローラーを置き換える機能を追加 ### Fixed diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index a01e411c..df7341ab 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - +- [#1482] Added support for replacing pre-existing animator controllers to `Merge Animator` +- ### Fixed ### Changed diff --git a/CHANGELOG-jp.md b/CHANGELOG-jp.md index cf49b41e..435bd08a 100644 --- a/CHANGELOG-jp.md +++ b/CHANGELOG-jp.md @@ -10,6 +10,7 @@ Modular Avatarの主な変更点をこのファイルで記録しています。 ### Added - CHANGELOGファイルを追加 +- [#1482] `Merge Animator` に既存のアニメーターコントローラーを置き換える機能を追加 ### Fixed - [#1460] パラメーターアセットをMA Parametersにインポートするとき、ローカルのみのパラメーターが間違ってアニメーターのみ扱いになる問題を修正 diff --git a/CHANGELOG.md b/CHANGELOG.md index 66fd48e0..d116a200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added CHANGELOG files +- [#1482] Added support for replacing pre-existing animator controllers to `Merge Animator` ### Fixed - [#1460] When importing parameter assets in MA Parameters, "local only" parameters were incorrectly treated as diff --git a/Editor/Inspector/MergeAnimationEditor.cs b/Editor/Inspector/MergeAnimationEditor.cs index 4aef150b..a1c064e0 100644 --- a/Editor/Inspector/MergeAnimationEditor.cs +++ b/Editor/Inspector/MergeAnimationEditor.cs @@ -11,6 +11,12 @@ namespace nadena.dev.modular_avatar.core.editor protected override string localizationPrefix => "path_mode"; } + [CustomPropertyDrawer(typeof(MergeAnimatorMode))] + internal class MergeModeDrawer : EnumDrawer + { + protected override string localizationPrefix => "merge_animator.merge_mode"; + } + [CustomEditor(typeof(ModularAvatarMergeAnimator))] class MergeAnimationEditor : MAEditorBase { @@ -20,7 +26,8 @@ namespace nadena.dev.modular_avatar.core.editor prop_pathMode, prop_matchAvatarWriteDefaults, prop_relativePathRoot, - prop_layerPriority; + prop_layerPriority, + prop_mergeMode; private void OnEnable() { @@ -34,6 +41,7 @@ namespace nadena.dev.modular_avatar.core.editor prop_relativePathRoot = serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.relativePathRoot)); prop_layerPriority = serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.layerPriority)); + prop_mergeMode = serializedObject.FindProperty(nameof(ModularAvatarMergeAnimator.mergeAnimatorMode)); } protected override void OnInnerInspectorGUI() @@ -47,8 +55,12 @@ namespace nadena.dev.modular_avatar.core.editor if (prop_pathMode.enumValueIndex == (int) MergeAnimatorPathMode.Relative) EditorGUILayout.PropertyField(prop_relativePathRoot, G("merge_animator.relative_path_root")); EditorGUILayout.PropertyField(prop_layerPriority, G("merge_animator.layer_priority")); - EditorGUILayout.PropertyField(prop_matchAvatarWriteDefaults, - G("merge_animator.match_avatar_write_defaults")); + EditorGUILayout.PropertyField(prop_mergeMode, G("merge_animator.merge_mode")); + using (new EditorGUI.DisabledScope(prop_mergeMode.enumValueIndex == (int)MergeAnimatorMode.Replace)) + { + EditorGUILayout.PropertyField(prop_matchAvatarWriteDefaults, + G("merge_animator.match_avatar_write_defaults")); + } serializedObject.ApplyModifiedProperties(); diff --git a/Editor/Localization/en-US.json b/Editor/Localization/en-US.json index e01648df..4f05dd76 100644 --- a/Editor/Localization/en-US.json +++ b/Editor/Localization/en-US.json @@ -78,6 +78,9 @@ "merge_animator.relative_path_root.tooltip": "The root object to use when interpreting relative paths. If not specified, the object this component is attached to will be used.", "merge_animator.layer_priority": "Layer Priority", "merge_animator.layer_priority.tooltip": "Controls the order in which layers are merged into the animator - lower to higher. Negative values are merged before the original layer on the avatar descriptor, while zero and positive numbers are merged after.", + "merge_animator.merge_mode": "Merge Mode", + "merge_animator.merge_mode.Append": "Append to Animator", + "merge_animator.merge_mode.Replace": "Replace Existing Animator", "merge_armature.lockmode": "Position sync mode", "merge_armature.lockmode.not_locked.title": "Not locked", "merge_armature.lockmode.not_locked.body": "Merged armature does not sync its position with the base avatar.", @@ -154,6 +157,8 @@ "error.replace_object.replacing_replacement": "[MA-0009] The same target object cannot be specified in multiple Replace Object components", "error.replace_object.parent_of_target": "[MA-0010] The target object cannot be a parent of this object", "error.singleton": "[MA-0011] Only one instance of {0} is allowed in an avatar", + "error.merge_animator.multiple_replacements": "[MA-0012] Multiple Merge Animators are trying to replace the same layer", + "error.merge_animator.multiple_replacements:hint": "Because the 'Replace' mode of Merge Animator replaces the entire animator with a different one, it's not clear which of these you wanted to have win. Try either deleting all but one of these Merge Animators, or setting them to Append mode.", "validation.blendshape_sync.no_local_renderer": "[MA-1000] No renderer found on this object", "validation.blendshape_sync.no_local_renderer:hint": "Blendshape Sync acts on a Skinned Mesh Renderer on the same GameObject. Did you attach it to the right object?", "validation.blendshape_sync.no_local_mesh": "[MA-1001] No mesh found on the renderer on this object", diff --git a/Editor/Localization/ja-JP.json b/Editor/Localization/ja-JP.json index 269545ff..539c6ae4 100644 --- a/Editor/Localization/ja-JP.json +++ b/Editor/Localization/ja-JP.json @@ -74,6 +74,9 @@ "merge_animator.relative_path_root.tooltip": "相対的パスはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。", "merge_animator.layer_priority": "レイヤー統合優先度", "merge_animator.layer_priority.tooltip": "アニメーターにレイヤーが統合される順番を制御します。低い値から高い値の順に統合されます。マイナスの場合は元々のAvatar Descriptorについているコントローラーより前に統合され、ゼロ以上の場合はそのあとに統合されます。", + "merge_animator.merge_mode": "統合モード", + "merge_animator.merge_mode.Append": "アニメーターに追加", + "merge_animator.merge_mode.Replace": "既存アニメーターを置き換える", "merge_armature.lockmode": "位置追従モード", "merge_armature.lockmode.not_locked.title": "追従なし", "merge_armature.lockmode.not_locked.body": "統合されるアーマチュアは、統合先のアーマチュアに追従しません。", diff --git a/Editor/MergeAnimatorProcessor.cs b/Editor/MergeAnimatorProcessor.cs index 9e579615..6973c962 100644 --- a/Editor/MergeAnimatorProcessor.cs +++ b/Editor/MergeAnimatorProcessor.cs @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.Linq; +using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf.animator; using UnityEditor; using UnityEngine; @@ -86,22 +87,37 @@ namespace nadena.dev.modular_avatar.core.editor List toMerge ) { - // Stable sort - var sorted = toMerge.OrderBy(x => x.layerPriority) - .ToList(); - var beforeOriginal = sorted.Where(x => x.layerPriority < 0) - .ToList(); - var afterOriginal = sorted.Where(x => x.layerPriority >= 0) + // Layer priority sorting is handled by NDMF, so we just need to worry about replace mode going first + var sorted = toMerge.OrderBy(x => x.mergeAnimatorMode == MergeAnimatorMode.Append) .ToList(); var controller = _asc.ControllerContext.Controllers[layerType]; + + var replacements = sorted.Count(x => x.mergeAnimatorMode == MergeAnimatorMode.Replace); + if (replacements > 1) + { + BuildReport.LogFatal("error.merge_animator.multiple_replacements", + sorted.Where(x => x.mergeAnimatorMode == MergeAnimatorMode.Replace).ToArray()); + } + else if (replacements == 1) + { + // Delete all pre-existing layers. + controller.RemoveLayers(_ => true); + + // Merge just the first controller (the one that replaces) + MergeSingle(context, controller, sorted.First(), null); + sorted.RemoveAt(0); + + // We'll now continue processing the rest as normal. + } + + bool? writeDefaults = null; var wdStateCounter = controller.Layers.SelectMany(l => l.StateMachine.AllStates()) .Select(s => s.WriteDefaultValues) .GroupBy(b => b) .ToDictionary(g => g.Key, g => g.Count()); - bool? writeDefaults = null; if (wdStateCounter.Count == 1) writeDefaults = wdStateCounter.First().Key; foreach (var component in sorted) diff --git a/Runtime/ModularAvatarMergeAnimator.cs b/Runtime/ModularAvatarMergeAnimator.cs index fde947d1..e06a0fba 100644 --- a/Runtime/ModularAvatarMergeAnimator.cs +++ b/Runtime/ModularAvatarMergeAnimator.cs @@ -37,6 +37,12 @@ namespace nadena.dev.modular_avatar.core Absolute } + public enum MergeAnimatorMode + { + Append, + Replace + } + [AddComponentMenu("Modular Avatar/MA Merge Animator")] [HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-animator?lang=auto")] public class ModularAvatarMergeAnimator : AvatarTagComponent, IVirtualizeAnimatorController @@ -51,7 +57,8 @@ namespace nadena.dev.modular_avatar.core public bool matchAvatarWriteDefaults; public AvatarObjectReference relativePathRoot = new AvatarObjectReference(); public int layerPriority = 0; - + public MergeAnimatorMode mergeAnimatorMode = MergeAnimatorMode.Append; + public override void ResolveReferences() { // no-op diff --git a/UnitTests~/MergeAnimatorTests/Replacement.meta b/UnitTests~/MergeAnimatorTests/Replacement.meta new file mode 100644 index 00000000..1322cda2 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d24638d75060c2b488455fc884562fc3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/Replacement/AC1.controller b/UnitTests~/MergeAnimatorTests/Replacement/AC1.controller new file mode 100644 index 00000000..59e90b8d --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/AC1.controller @@ -0,0 +1,72 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-5473590201038751078 +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: [] + 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: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AC1 + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: 1 + m_StateMachine: {fileID: 6035820398382803214} + 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!1107 &6035820398382803214 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: 1 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -5473590201038751078} + m_Position: {x: 410, y: 260, 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: -5473590201038751078} diff --git a/UnitTests~/MergeAnimatorTests/Replacement/AC1.controller.meta b/UnitTests~/MergeAnimatorTests/Replacement/AC1.controller.meta new file mode 100644 index 00000000..46d4bdde --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/AC1.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d68bd7182dbae1d49a1d2f642d5a7af0 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/Replacement/AC2.controller b/UnitTests~/MergeAnimatorTests/Replacement/AC2.controller new file mode 100644 index 00000000..c05b72e3 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/AC2.controller @@ -0,0 +1,72 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-5473590201038751078 +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: [] + 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: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AC2 + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: 2 + m_StateMachine: {fileID: 6035820398382803214} + 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!1107 &6035820398382803214 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: 2 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -5473590201038751078} + m_Position: {x: 410, y: 260, 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: -5473590201038751078} diff --git a/UnitTests~/MergeAnimatorTests/Replacement/AC2.controller.meta b/UnitTests~/MergeAnimatorTests/Replacement/AC2.controller.meta new file mode 100644 index 00000000..eecd0989 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/AC2.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 728acb7ebd30a2c409671d4de47a5a3e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/Replacement/AC3.controller b/UnitTests~/MergeAnimatorTests/Replacement/AC3.controller new file mode 100644 index 00000000..6466fda8 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/AC3.controller @@ -0,0 +1,72 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-5473590201038751078 +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: [] + 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: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AC3 + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: 3 + m_StateMachine: {fileID: 6035820398382803214} + 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!1107 &6035820398382803214 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: 3 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -5473590201038751078} + m_Position: {x: 410, y: 260, 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: -5473590201038751078} diff --git a/UnitTests~/MergeAnimatorTests/Replacement/AC3.controller.meta b/UnitTests~/MergeAnimatorTests/Replacement/AC3.controller.meta new file mode 100644 index 00000000..9f7a4aa1 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/AC3.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b3df8382c3aea04bbb2fc60b74ee201 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs new file mode 100644 index 00000000..dedbcab3 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs @@ -0,0 +1,59 @@ +using System.Linq; +using modular_avatar_tests; +using nadena.dev.modular_avatar.core; +using nadena.dev.ndmf; +using NUnit.Framework; +using UnityEditor.Animations; +using AvatarProcessor = nadena.dev.modular_avatar.core.editor.AvatarProcessor; + +namespace UnitTests.MergeAnimatorTests.Replacement +{ + public class MergeAnimatorReplacementTest : TestBase + { + [Test] + public void MergeWithReplacement() + { + var prefab = CreatePrefab("happycase.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + Assert.AreEqual(2, fxc.layers.Length); + Assert.AreEqual("2", fxc.layers[0].name); + Assert.AreEqual("3", fxc.layers[1].name); + } + + [Test] + public void ReplaceRespectsPriorities() + { + var prefab = CreatePrefab("happycase.prefab"); + var merge = prefab.transform.Find("merge_replace").GetComponent(); + merge.layerPriority = 1; + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + Assert.AreEqual(2, fxc.layers.Length); + Assert.AreEqual("3", fxc.layers[0].name); + Assert.AreEqual("2", fxc.layers[1].name); + } + + [Test] + public void MultipleReplacementIsForbidden() + { + Assert.Ignore("https://github.com/bdunderscore/ndmf/issues/547"); + var prefab = CreatePrefab("doublereplace.prefab"); + + var errors = ErrorReport.CaptureErrors(() => AvatarProcessor.ProcessAvatar(prefab)); + + Assert.AreEqual(1, errors.Count()); + var err = (SimpleError)errors.First().TheError; + Assert.AreEqual("error.merge_animator.multiple_replacements", err.TitleKey); + Assert.AreEqual(2, err._references.Count()); + } + } +} \ No newline at end of file diff --git a/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs.meta b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs.meta new file mode 100644 index 00000000..fe0e5b0f --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0789e1f9a323474a87b151fccf66f98a +timeCreated: 1741659972 \ No newline at end of file diff --git a/UnitTests~/MergeAnimatorTests/Replacement/doublereplace.prefab b/UnitTests~/MergeAnimatorTests/Replacement/doublereplace.prefab new file mode 100644 index 00000000..f7eb36e5 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/doublereplace.prefab @@ -0,0 +1,435 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1020444813144676188 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7612583006210828642} + - component: {fileID: 8105255187856184770} + m_Layer: 0 + m_Name: merge_replace + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7612583006210828642 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1020444813144676188} + 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: 4693460015573334593} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8105255187856184770 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1020444813144676188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 728acb7ebd30a2c409671d4de47a5a3e, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 + mergeAnimatorMode: 1 +--- !u!1 &5288809495372619585 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5482927557368973391} + - component: {fileID: 3363614068071245935} + m_Layer: 0 + m_Name: merge_add + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5482927557368973391 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5288809495372619585} + 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: 4693460015573334593} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3363614068071245935 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5288809495372619585} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 2b3df8382c3aea04bbb2fc60b74ee201, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 + mergeAnimatorMode: 1 +--- !u!1 &6377101953255365293 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4693460015573334593} + - component: {fileID: 4270628243358584117} + - component: {fileID: 6952489307538536204} + - component: {fileID: 7015116864136794277} + m_Layer: 0 + m_Name: doublereplace + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4693460015573334593 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6377101953255365293} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.32464555, y: 0.48326847, z: -0.26786608} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 5482927557368973391} + - {fileID: 7612583006210828642} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &4270628243358584117 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6377101953255365293} + 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 &6952489307538536204 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6377101953255365293} + 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: 1 + 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: 9100000, guid: d68bd7182dbae1d49a1d2f642d5a7af0, + type: 2} + mask: {fileID: 0} + isDefault: 0 + 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 &7015116864136794277 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6377101953255365293} + 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/doublereplace.prefab.meta b/UnitTests~/MergeAnimatorTests/Replacement/doublereplace.prefab.meta new file mode 100644 index 00000000..d7180280 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/doublereplace.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 91d8e312f4015eb4bbdd64f960598bee +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/Replacement/happycase.prefab b/UnitTests~/MergeAnimatorTests/Replacement/happycase.prefab new file mode 100644 index 00000000..5b3c9218 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/happycase.prefab @@ -0,0 +1,435 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1126862795880074418 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6746842210895201950} + - component: {fileID: 932087599971471624} + m_Layer: 0 + m_Name: merge_replace + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6746842210895201950 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1126862795880074418} + 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: 1094934460896273879} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &932087599971471624 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1126862795880074418} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 728acb7ebd30a2c409671d4de47a5a3e, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 + mergeAnimatorMode: 1 +--- !u!1 &1546408403991313171 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4220119272987791400} + - component: {fileID: 1903493894714097121} + m_Layer: 0 + m_Name: merge_add + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4220119272987791400 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1546408403991313171} + 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: 1094934460896273879} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1903493894714097121 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1546408403991313171} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 2b3df8382c3aea04bbb2fc60b74ee201, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 + mergeAnimatorMode: 0 +--- !u!1 &3144332405880172657 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1094934460896273879} + - component: {fileID: 8565040130466491148} + - component: {fileID: 9201479331307309647} + - component: {fileID: 6484578555856173614} + m_Layer: 0 + m_Name: happycase + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1094934460896273879 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3144332405880172657} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.32464555, y: 0.48326847, z: -0.26786608} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4220119272987791400} + - {fileID: 6746842210895201950} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &8565040130466491148 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3144332405880172657} + 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 &9201479331307309647 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3144332405880172657} + 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: 1 + 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: 9100000, guid: d68bd7182dbae1d49a1d2f642d5a7af0, + type: 2} + mask: {fileID: 0} + isDefault: 0 + 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 &6484578555856173614 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3144332405880172657} + 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/happycase.prefab.meta b/UnitTests~/MergeAnimatorTests/Replacement/happycase.prefab.meta new file mode 100644 index 00000000..e228b62c --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/Replacement/happycase.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 763464231fa8fb44697dcf35e451a74a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/docs~/docs/reference/merge-animator.md b/docs~/docs/reference/merge-animator.md index 772a6b48..b19a8771 100644 --- a/docs~/docs/reference/merge-animator.md +++ b/docs~/docs/reference/merge-animator.md @@ -43,6 +43,24 @@ control which object is used as the root for paths in animations by setting the If you want to animate objects that are already attached to the avatar (that aren't under your object), set the path mode to "Absolute". This will cause the animator to use absolute paths, and will not attempt to interpret paths relative to the Merge Animator component. This means you will need to record your animations using the avatar's root animator instead. +### Layer Priority + +Layer priority controls the order in which Merge Animators are applied. They will be placed in the final animator in +increasing order of priority (that is, lower numbers are first in animator order, and higher numbers override them). +Merge Animators with the same priority will be placed in the order they are in the hierarchy. Any pre-existing animator +is considered to be at priority zero, before all priority-zero Merge Animators. + +### Merge Mode + +By default, Merge Animator will add the animator to the specified layer. If you want to replace the layer instead, set +the merge mode to "Replace Existing Animator". This will replace any animator configured on the VRChat Avatar Descriptor +with the one you provide. + +The replaced animator will keep the priority you specified, but it will be applied before any other Merge Animators +at that priority level. + +Having multiple Merge Animators set to the same Layer Type and Replace mode will result in an error. + ### Write Defaults By default, the write defaults state of your animator will not be changed. If you want to ensure that the WD settings of your animator states always matches the avatar's animator, click "Match Avatar Write Defaults". diff --git a/docs~/docs/reference/merge-animator.png b/docs~/docs/reference/merge-animator.png index b79f42bd..80942a5b 100644 Binary files a/docs~/docs/reference/merge-animator.png and b/docs~/docs/reference/merge-animator.png differ diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md index 80361b7e..be4eacff 100644 --- a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md @@ -31,7 +31,7 @@ GameObjectにAnimatorコンポーネントも追加して、Animationパネル ![Recording an animation using Merge Animator](merge-animator-record.png) 開発の補助として、「付属アニメーターを削除」を入れると、同じオブジェクト内のAnimatorが自動的に排除されます。 - +. ### ヒューマノイドボーンのアニメーション ヒューマノイドボーンを操作するアニメーションは上記の相対的なパスで解釈されるのではなく、アバター全体に適用されます。 @@ -45,6 +45,23 @@ AFKアニメーションなどほとんどのヒューマノイドアニメー これでアニメーターの中のパスがアバターを基準に解釈され、Merge Animatorから相対的に解釈されるシステムがはずされます。 上記の収録方法が使えなくなるわけです。 +### レイヤー優先度 + +レイヤー優先度は、統合されるアニメーターが適用される順番を制御します。優先度が低いものから順にアニメーターが適用され、 +高いものが低いものの効果を上書きします。 +同じ優先度のMerge Animatorは、階層の順に適用されます。既存のアニメーターは、優先度がゼロと過程され、全てのゼロ優先度の +Merge Animatorよりも前に適用されます。 + +### 統合モード + +デフォルトでは、Merge Animatorは指定したレイヤーにアニメーターを追加します。レイヤーを置き換えたい場合は、 +「既存アニメーターを置き換える」にしてください。これで、VRChatアバターディスクリプタに設定されたアニメーターが、 +指定されたアニメーターに置き換わります。 + +置き換わったアニメーターは、指定した優先度を保持しますが、同じ優先度の他のMerge Animatorよりも前に適用されます。 + +同じレイヤー種別で複数のアニメーターを「置き換える」設定にしてしまうとエラーになります。 + ### Write Defaults デフォルトでは、アニメーターの中のWrite Defaults設定がそのまま使われます。アバターに合わせたい場合は、「アバターのWriteDefaults設定に合わせる」を入れてください。 diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.png b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.png index 65e5b445..126dfe7c 100644 Binary files a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.png and b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.png differ