From a8c04fb63709769a62b9cbbd3dd234ae3f1211f0 Mon Sep 17 00:00:00 2001 From: bd_ Date: Sun, 17 Nov 2024 14:22:23 -0800 Subject: [PATCH] Implement animator state machine transitions --- .../AnimatorServicesContext.cs | 2 +- Editor/API/AnimatorServices/CloneContext.cs | 3 +- .../VirtualObjects/VirtualStateMachine.cs | 24 ++++++ .../VirtualObjects/VirtualStateTransition.cs | 5 ++ .../VirtualObjects/VirtualTransition.cs | 4 + .../VirtualObjects/VirtualTransitionBase.cs | 4 +- .../AnimationServices/StateGraphTest.cs | 85 ++++++++++++++++++- .../VirtualStateMachineTest.cs | 2 - 8 files changed, 122 insertions(+), 7 deletions(-) diff --git a/Editor/API/AnimatorServices/AnimatorServicesContext.cs b/Editor/API/AnimatorServices/AnimatorServicesContext.cs index 6f4d704..8eb9310 100644 --- a/Editor/API/AnimatorServices/AnimatorServicesContext.cs +++ b/Editor/API/AnimatorServices/AnimatorServicesContext.cs @@ -57,7 +57,7 @@ public VirtualAnimatorController this[object key] if (state.virtualController == null) { - state.virtualController = VirtualAnimatorController.Clone(_cloneContext, state.originalController); + state.virtualController = _cloneContext.Clone(state.originalController); } return state.virtualController; diff --git a/Editor/API/AnimatorServices/CloneContext.cs b/Editor/API/AnimatorServices/CloneContext.cs index 849afd4..8e15210 100644 --- a/Editor/API/AnimatorServices/CloneContext.cs +++ b/Editor/API/AnimatorServices/CloneContext.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using UnityEditor.Animations; using UnityEngine; +using Object = UnityEngine.Object; namespace nadena.dev.ndmf.animator { @@ -82,7 +83,7 @@ private U GetOrClone(T key, Func clone) where U : clas { _cloneDepth++; - if (key == null) return null; + if (key == null || (key is Object obj && obj == null)) return null; if (TryGetValue(key, out U value)) return value; value = clone(this, key); _clones[key] = value; diff --git a/Editor/API/AnimatorServices/VirtualObjects/VirtualStateMachine.cs b/Editor/API/AnimatorServices/VirtualObjects/VirtualStateMachine.cs index 7d3a7d3..3e39ab0 100644 --- a/Editor/API/AnimatorServices/VirtualObjects/VirtualStateMachine.cs +++ b/Editor/API/AnimatorServices/VirtualObjects/VirtualStateMachine.cs @@ -48,6 +48,13 @@ public static VirtualStateMachine Clone(CloneContext context, AnimatorStateMachi State = context.Clone(s.state), Position = s.position }).ToImmutableList(); + + vsm.StateMachineTransitions = stateMachine.stateMachines + .ToImmutableDictionary( + sm => context.Clone(sm.stateMachine), + sm => stateMachine.GetStateMachineTransitions(sm.stateMachine) + .Select(context.Clone).ToImmutableList() + ); }); return vsm; @@ -72,6 +79,7 @@ private VirtualStateMachine(CloneContext context, string name = "") Behaviours = ImmutableList.Empty; StateMachines = ImmutableList.Empty; States = ImmutableList.Empty; + StateMachineTransitions = ImmutableDictionary>.Empty; } AnimatorStateMachine ICommitable.Prepare(CommitContext context) @@ -108,6 +116,14 @@ void ICommitable.Commit(CommitContext context, AnimatorSta .ToArray(); // DefaultState will be overwritten if we set it too soon; set it last. obj.defaultState = context.CommitObject(DefaultState); + + foreach (var (sm, transitions) in StateMachineTransitions) + { + obj.SetStateMachineTransitions( + context.CommitObject(sm), + transitions.Select(t => (AnimatorTransition)context.CommitObject(t)).ToArray() + ); + } } private string _name; @@ -190,6 +206,14 @@ public ImmutableList States set => _states = I(value); } + private ImmutableDictionary> _stateMachineTransitions; + + public ImmutableDictionary> StateMachineTransitions + { + get => _stateMachineTransitions; + set => _stateMachineTransitions = I(value); + } + private VirtualState _defaultState; public VirtualState DefaultState diff --git a/Editor/API/AnimatorServices/VirtualObjects/VirtualStateTransition.cs b/Editor/API/AnimatorServices/VirtualObjects/VirtualStateTransition.cs index d4311e0..b3ffa52 100644 --- a/Editor/API/AnimatorServices/VirtualObjects/VirtualStateTransition.cs +++ b/Editor/API/AnimatorServices/VirtualObjects/VirtualStateTransition.cs @@ -11,6 +11,11 @@ internal VirtualStateTransition(CloneContext context, AnimatorStateTransition cl _stateTransition = cloned; } + public VirtualStateTransition() : base(null, new AnimatorStateTransition()) + { + _stateTransition = (AnimatorStateTransition)_transition; + } + public static VirtualStateTransition Clone( CloneContext context, AnimatorStateTransition transition diff --git a/Editor/API/AnimatorServices/VirtualObjects/VirtualTransition.cs b/Editor/API/AnimatorServices/VirtualObjects/VirtualTransition.cs index 961eb05..ca06db4 100644 --- a/Editor/API/AnimatorServices/VirtualObjects/VirtualTransition.cs +++ b/Editor/API/AnimatorServices/VirtualObjects/VirtualTransition.cs @@ -8,6 +8,10 @@ internal VirtualTransition(CloneContext context, AnimatorTransitionBase cloned) { } + public VirtualTransition() : base(null, new AnimatorTransition()) + { + } + public static VirtualTransition Clone( CloneContext context, AnimatorTransition transition diff --git a/Editor/API/AnimatorServices/VirtualObjects/VirtualTransitionBase.cs b/Editor/API/AnimatorServices/VirtualObjects/VirtualTransitionBase.cs index a8b9207..a41de2e 100644 --- a/Editor/API/AnimatorServices/VirtualObjects/VirtualTransitionBase.cs +++ b/Editor/API/AnimatorServices/VirtualObjects/VirtualTransitionBase.cs @@ -8,14 +8,14 @@ namespace nadena.dev.ndmf.animator { public class VirtualTransitionBase : VirtualNode, ICommitable, IDisposable { - private AnimatorTransitionBase _transition; + protected AnimatorTransitionBase _transition; private ImmutableList _conditions; internal VirtualTransitionBase(CloneContext context, AnimatorTransitionBase cloned) { _transition = cloned; - context.DeferCall(() => + context?.DeferCall(() => { if (cloned.destinationState != null) { diff --git a/UnitTests~/AnimationServices/StateGraphTest.cs b/UnitTests~/AnimationServices/StateGraphTest.cs index c1edaef..711e5cd 100644 --- a/UnitTests~/AnimationServices/StateGraphTest.cs +++ b/UnitTests~/AnimationServices/StateGraphTest.cs @@ -1,4 +1,5 @@ -using nadena.dev.ndmf.animator; +using System.Collections.Immutable; +using nadena.dev.ndmf.animator; using NUnit.Framework; using UnityEditor.Animations; @@ -86,5 +87,87 @@ public void TestStateGraphConvergence() Assert.AreEqual(committedS3, committedS2.transitions[1].destinationState); Assert.IsTrue(committedS2.transitions[2].isExit); } + + [Test] + public void TestStateMachineTransitions() + { + var sm1 = new AnimatorStateMachine() { name = "sm1" }; + var sm2 = new AnimatorStateMachine() { name = "sm2" }; + var sm3 = new AnimatorStateMachine() { name = "sm3" }; + + var s1 = new AnimatorState() { name = "s1" }; + var s2 = new AnimatorState() { name = "s2" }; + + sm1.stateMachines = new[] + { + new ChildAnimatorStateMachine() + { + stateMachine = sm2 + }, + new ChildAnimatorStateMachine() + { + stateMachine = sm3 + } + }; + sm1.states = new[] + { + new ChildAnimatorState() + { + state = s1 + }, + new ChildAnimatorState() + { + state = s2 + } + }; + + sm1.SetStateMachineTransitions(sm2, new[] + { + new AnimatorTransition() + { + destinationState = s1, + conditions = new AnimatorCondition[0] + } + }); + + var cloneContext = new CloneContext(new GenericPlatformAnimatorBindings()); + var clonedSM1 = cloneContext.Clone(sm1); + + Assert.AreEqual(clonedSM1.StateMachines.Count, 2); + var clonedSM2 = clonedSM1.StateMachines[0].StateMachine; + var clonedSM3 = clonedSM1.StateMachines[1].StateMachine; + + var clonedS1 = clonedSM1.States[0].State; + var clonedS2 = clonedSM1.States[1].State; + + Assert.AreEqual(clonedSM1.StateMachineTransitions.Count, 2); + Assert.AreEqual(clonedS1, clonedSM1.StateMachineTransitions[clonedSM2][0].DestinationState); + Assert.AreEqual(0, clonedSM1.StateMachineTransitions[clonedSM3].Count); + + var vt = new VirtualTransition(); + vt.SetDestination(clonedS2); + + clonedSM1.StateMachineTransitions = clonedSM1.StateMachineTransitions.SetItem( + clonedSM3, + ImmutableList.Empty.Add(vt) + ); + + var commitContext = new CommitContext(); + var outSM1 = commitContext.CommitObject(clonedSM1); + + var outSM2 = outSM1.stateMachines[0].stateMachine; + var outSM3 = outSM1.stateMachines[1].stateMachine; + + var outS1 = outSM1.states[0].state; + var outS2 = outSM1.states[1].state; + + var stateTransitions2 = outSM1.GetStateMachineTransitions(outSM2); + Assert.AreEqual(stateTransitions2.Length, 1); + Assert.AreEqual(outS1, stateTransitions2[0].destinationState); + + var stateTransitions3 = outSM1.GetStateMachineTransitions(outSM3); + Assert.AreEqual(stateTransitions3.Length, 1); + Assert.AreEqual(outS2, stateTransitions3[0].destinationState); + } } } \ No newline at end of file diff --git a/UnitTests~/AnimationServices/VirtualStateMachineTest.cs b/UnitTests~/AnimationServices/VirtualStateMachineTest.cs index 70080f6..c41a474 100644 --- a/UnitTests~/AnimationServices/VirtualStateMachineTest.cs +++ b/UnitTests~/AnimationServices/VirtualStateMachineTest.cs @@ -101,8 +101,6 @@ public void PreservesParentStateMachinePosition() virtualState => Assert.AreEqual(new Vector3(1, 2, 3), virtualState.ParentStateMachinePosition) ); } - - // TODO: object graph tests }