From 27f0557367d3f8c75321ef3b056037278e8eac04 Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 17 Feb 2024 19:19:35 +0900 Subject: [PATCH] fix: float conversion breaks sub-state-machine entry transitions, including FaceEmo (#674) * fix: float conversion breaks substatemachine entry transitions Closes: #669 * fix: unnecessary type conversions --- Editor/Animation/AnimationUtil.cs | 17 +- Editor/Animation/AnimatorCombiner.cs | 20 +- .../TypeAdjustment/ConvertTransitionTypes.cs | 36 ++-- .../TypeAdjustment/ac1.controller | 196 +++++++++++++++++- 4 files changed, 231 insertions(+), 38 deletions(-) diff --git a/Editor/Animation/AnimationUtil.cs b/Editor/Animation/AnimationUtil.cs index 6f01b246..61e09659 100644 --- a/Editor/Animation/AnimationUtil.cs +++ b/Editor/Animation/AnimationUtil.cs @@ -189,20 +189,18 @@ namespace nadena.dev.modular_avatar.animation || path.StartsWith("Packages/com.vrchat."); } + /// - /// Enumerates all states in an animator controller + /// Enumerates all state machines and sub-state machines starting from a specific starting ASM /// /// /// - internal static IEnumerable States(AnimatorController ac) + internal static IEnumerable ReachableStateMachines(this AnimatorStateMachine asm) { HashSet visitedStateMachines = new HashSet(); Queue pending = new Queue(); - - foreach (var layer in ac.layers) - { - if (layer.stateMachine != null) pending.Enqueue(layer.stateMachine); - } + + pending.Enqueue(asm); while (pending.Count > 0) { @@ -215,10 +213,7 @@ namespace nadena.dev.modular_avatar.animation if (child.stateMachine != null) pending.Enqueue(child.stateMachine); } - foreach (var state in next.states) - { - yield return state.state; - } + yield return next; } } } diff --git a/Editor/Animation/AnimatorCombiner.cs b/Editor/Animation/AnimatorCombiner.cs index cd6f56a7..419de050 100644 --- a/Editor/Animation/AnimatorCombiner.cs +++ b/Editor/Animation/AnimatorCombiner.cs @@ -130,18 +130,18 @@ namespace nadena.dev.modular_avatar.animation { foreach (var layer in _layers) { - foreach (var asset in layer.stateMachine.ReferencedAssets(includeScene: false)) + foreach (var asm in layer.stateMachine.ReachableStateMachines()) { - if (asset is AnimatorState s) + foreach (ChildAnimatorState s in asm.states) { - s.transitions = s.transitions.SelectMany(FixupTransition).ToArray(); + s.state.transitions = s.state.transitions.SelectMany(FixupTransition).ToArray(); } + + asm.entryTransitions = asm.entryTransitions + .SelectMany(FixupTransition).ToArray(); + asm.anyStateTransitions = asm.anyStateTransitions + .SelectMany(FixupTransition).ToArray(); } - - layer.stateMachine.entryTransitions = layer.stateMachine.entryTransitions - .SelectMany(FixupTransition).ToArray(); - layer.stateMachine.anyStateTransitions = layer.stateMachine.anyStateTransitions - .SelectMany(FixupTransition).ToArray(); } } @@ -376,6 +376,8 @@ namespace nadena.dev.modular_avatar.animation { if (_parameters.TryGetValue(param.name, out var acp)) { + if (acp.type == param.type) continue; + if (acp.type != param.type && (acp.type == AnimatorControllerParameterType.Trigger || param.type == AnimatorControllerParameterType.Trigger)) @@ -403,7 +405,7 @@ namespace nadena.dev.modular_avatar.animation defaultInt = param.defaultInt }; - _parameters.Add(param.name, param); + _parameters.Add(param.name, clonedParameter); _parameterSource.Add(param.name, controller); } diff --git a/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs b/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs index 720482b0..3aa640ea 100644 --- a/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs +++ b/UnitTests~/MergeAnimatorTests/TypeAdjustment/ConvertTransitionTypes.cs @@ -1,8 +1,8 @@ -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using modular_avatar_tests; +using nadena.dev.modular_avatar.core; using nadena.dev.ndmf; using NUnit.Framework; using UnityEditor.Animations; @@ -132,13 +132,34 @@ public class ConvertTransitionTypes : TestBase Assert.AreEqual(AnimatorControllerParameterType.Float, p_types["float"]); Assert.AreEqual(AnimatorControllerParameterType.Float, p_types["int2"]); } + + [Test] + public void SubStateMachineHandling() + { + var prefab = CreatePrefab("ConvertTransitionTypes.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var layer = findFxLayer(prefab, "sub_state_machine"); + + AssertSingleTransition(layer.stateMachine.entryTransitions[0], ("bool", AnimatorConditionMode.Greater, 0.5f)); + + var ssm1 = layer.stateMachine.stateMachines[0].stateMachine; + AssertSingleTransition(ssm1.entryTransitions[0], ("bool", AnimatorConditionMode.Greater, 0.5f)); + + var ssm2 = ssm1.stateMachines[0].stateMachine; + AssertSingleTransition(ssm2.entryTransitions[0], ("bool", AnimatorConditionMode.Greater, 0.5f)); + } [Test] public void NoConversionWhenConsistent() { var prefab = CreatePrefab("ConvertTransitionTypes.prefab"); - - UnityEngine.Object.DestroyImmediate(prefab.transform.Find("2").gameObject); + + var merge1 = prefab.transform.Find("1").GetComponent(); + var merge2 = prefab.transform.Find("2").GetComponent(); + + merge2.animator = merge1.animator; AvatarProcessor.ProcessAvatar(prefab); @@ -192,15 +213,6 @@ public class ConvertTransitionTypes : TestBase params (string, AnimatorConditionMode, float)[] conditions) { var srcState = FindStateInLayer(layer, src); - - foreach (var s in layer.stateMachine.states) - { - Debug.Log("$$$ State: " + s.state.name); - foreach (var t0 in s.state.transitions) - { - Debug.Log("$$$ => " + t0.destinationState.name); - } - } var transitions = srcState.transitions.Where(t2 => t2.destinationState.name == dest) .ToArray(); diff --git a/UnitTests~/MergeAnimatorTests/TypeAdjustment/ac1.controller b/UnitTests~/MergeAnimatorTests/TypeAdjustment/ac1.controller index 6c449628..42d2465f 100644 --- a/UnitTests~/MergeAnimatorTests/TypeAdjustment/ac1.controller +++ b/UnitTests~/MergeAnimatorTests/TypeAdjustment/ac1.controller @@ -1,5 +1,28 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1107 &-8678029029566323935 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New StateMachine + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -2368878104934149449} + m_Position: {x: 340, y: 120, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: + - {fileID: -6793735410602199853} + 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: -2368878104934149449} --- !u!1102 &-8277159016159921547 AnimatorState: serializedVersion: 6 @@ -54,6 +77,23 @@ AnimatorStateMachine: m_ExitPosition: {x: 800, y: 120, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} m_DefaultState: {fileID: -3477782397476088704} +--- !u!1109 &-6793735410602199853 +AnimatorTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: bool + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -2368878104934149449} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 1 --- !u!1101 &-6702605715029525197 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -366,6 +406,55 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1102 &-2368878104934149449 +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!1107 &-2155158836370751539 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New StateMachine + m_ChildStates: [] + m_ChildStateMachines: + - serializedVersion: 1 + m_StateMachine: {fileID: -8678029029566323935} + m_Position: {x: 370, y: 100, z: 0} + m_AnyStateTransitions: [] + m_EntryTransitions: + - {fileID: 9064003435054199019} + 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: -2368878104934149449} --- !u!1101 &-1545596635161646672 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -431,37 +520,37 @@ AnimatorController: m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: int m_Type: 3 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: float m_Type: 1 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: int2 m_Type: 3 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: int3 m_Type: 3 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: trigger m_Type: 9 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} m_AnimatorLayers: - serializedVersion: 5 m_Name: int transitions @@ -535,6 +624,18 @@ AnimatorController: m_IKPass: 0 m_SyncedLayerAffectsTiming: 0 m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: sub_state_machine + m_StateMachine: {fileID: 7068830732163781358} + 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!1102 &308203126016203391 AnimatorState: serializedVersion: 6 @@ -639,6 +740,31 @@ AnimatorStateMachine: m_ExitPosition: {x: 800, y: 120, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} m_DefaultState: {fileID: 2331480681644977070} +--- !u!1101 &1712637675519008892 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: bool + m_EventTreshold: 0 + m_DstStateMachine: {fileID: -8678029029566323935} + m_DstState: {fileID: 0} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1107 &2101233559568525048 AnimatorStateMachine: serializedVersion: 6 @@ -832,6 +958,23 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1109 &6284330602530841453 +AnimatorTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: bool + m_EventTreshold: 0 + m_DstStateMachine: {fileID: -2155158836370751539} + m_DstState: {fileID: 0} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 1 --- !u!1109 &6482949225251473226 AnimatorTransition: m_ObjectHideFlags: 1 @@ -874,6 +1017,30 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1107 &7068830732163781358 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: sub_state_machine + m_ChildStates: [] + m_ChildStateMachines: + - serializedVersion: 1 + m_StateMachine: {fileID: -2155158836370751539} + m_Position: {x: 300, y: 170, z: 0} + m_AnyStateTransitions: + - {fileID: 1712637675519008892} + m_EntryTransitions: + - {fileID: 6284330602530841453} + 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: -2368878104934149449} --- !u!1101 &7085359437149011441 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -1058,3 +1225,20 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1109 &9064003435054199019 +AnimatorTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: bool + m_EventTreshold: 0 + m_DstStateMachine: {fileID: -8678029029566323935} + m_DstState: {fileID: 0} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 1