From 698a9f193800a2e225ad901faffcb93632ebf84f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Apr 2023 10:47:10 +0200 Subject: [PATCH] Add **Interruption options** to State Machine transition --- .../Archetypes/Animation.StateMachine.cs | 94 +++++++++++-------- Source/Engine/Animations/Graph/AnimGraph.h | 17 +--- .../Animations/Graph/AnimGroup.Animation.cpp | 56 ++++++++--- 3 files changed, 103 insertions(+), 64 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 52c3bcb4e..c56b5dfe4 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -1552,7 +1552,7 @@ namespace FlaxEditor.Surface.Archetypes : base(id, context, nodeArch, groupArch) { } - + /// public override int TransitionsDataIndex => 0; } @@ -1564,6 +1564,28 @@ namespace FlaxEditor.Surface.Archetypes /// internal class StateMachineTransition : ISurfaceContext { + /// + /// State transition interruption flags. + /// + [Flags] + public enum InterruptionFlags + { + /// + /// Nothing. + /// + None = 0, + + /// + /// Transition rule will be rechecked during active transition with option to interrupt transition. + /// + RuleRechecking = 1, + + /// + /// Interrupted transition is immediately stopped without blending out. + /// + Instant = 2, + } + /// /// The packed data container for the transition data storage. Helps with serialization and versioning the data. /// Must match AnimGraphBase::LoadStateTransition in C++ @@ -1574,31 +1596,16 @@ namespace FlaxEditor.Surface.Archetypes [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 32)] internal struct Data { - /// - /// The transition flag types. - /// + // Must match AnimGraphStateTransition::FlagTypes [Flags] public enum FlagTypes { - /// - /// The none. - /// None = 0, - - /// - /// The enabled flag. - /// Enabled = 1, - - /// - /// The solo flag. - /// Solo = 2, - - /// - /// The use default rule flag. - /// UseDefaultRule = 4, + InterruptionRuleRechecking = 8, + InterruptionInstant = 16, } /// @@ -1641,21 +1648,11 @@ namespace FlaxEditor.Surface.Archetypes /// public int Unused2; - /// - /// Determines whether the data has a given flag set. - /// - /// The flag. - /// true if the specified flag is set; otherwise, false. public bool HasFlag(FlagTypes flag) { return (Flags & flag) == flag; } - /// - /// Sets the flag to the given value. - /// - /// The flag. - /// If set to true the flag will be set, otherwise it will be cleared. public void SetFlag(FlagTypes flag, bool value) { if (value) @@ -1683,7 +1680,7 @@ namespace FlaxEditor.Surface.Archetypes /// /// If checked, the transition can be triggered, otherwise it will be ignored. /// - [EditorOrder(10), DefaultValue(true), Tooltip("If checked, the transition can be triggered, otherwise it will be ignored.")] + [EditorOrder(10), DefaultValue(true)] public bool Enabled { get => _data.HasFlag(Data.FlagTypes.Enabled); @@ -1698,7 +1695,7 @@ namespace FlaxEditor.Surface.Archetypes /// /// If checked, animation graph will ignore other transitions from the source state and use only this transition. /// - [EditorOrder(20), DefaultValue(false), Tooltip("If checked, animation graph will ignore other transitions from the source state and use only this transition.")] + [EditorOrder(20), DefaultValue(false)] public bool Solo { get => _data.HasFlag(Data.FlagTypes.Solo); @@ -1713,7 +1710,7 @@ namespace FlaxEditor.Surface.Archetypes /// /// If checked, animation graph will perform automatic transition based on the state animation pose (single shot animation play). /// - [EditorOrder(30), DefaultValue(false), Tooltip("If checked, animation graph will perform automatic transition based on the state animation pose (single shot animation play).")] + [EditorOrder(30), DefaultValue(false)] public bool UseDefaultRule { get => _data.HasFlag(Data.FlagTypes.UseDefaultRule); @@ -1725,9 +1722,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - /// The transition order (higher first). + /// The transition order. Transitions with the higher order are handled before the ones with the lower order. /// - [EditorOrder(40), DefaultValue(0), Tooltip("The transition order. Transitions with the higher order are handled before the ones with the lower order.")] + [EditorOrder(40), DefaultValue(0)] public int Order { get => _data.Order; @@ -1743,7 +1740,7 @@ namespace FlaxEditor.Surface.Archetypes /// /// The blend duration (in seconds). /// - [EditorOrder(50), DefaultValue(0.1f), Limit(0, 20.0f, 0.1f), Tooltip("Transition blend duration (in seconds).")] + [EditorOrder(50), DefaultValue(0.1f), Limit(0, 20.0f, 0.1f)] public float BlendDuration { get => _data.BlendDuration; @@ -1755,9 +1752,9 @@ namespace FlaxEditor.Surface.Archetypes } /// - /// The blend mode. + /// Transition blending mode for blend alpha. /// - [EditorOrder(60), DefaultValue(AlphaBlendMode.HermiteCubic), Tooltip("Transition blending mode for blend alpha.")] + [EditorOrder(60), DefaultValue(AlphaBlendMode.HermiteCubic)] public AlphaBlendMode BlendMode { get => _data.BlendMode; @@ -1768,6 +1765,29 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + /// Transition interruption options. + /// + [EditorOrder(70), DefaultValue(InterruptionFlags.None)] + public InterruptionFlags Interruption + { + get + { + var flags = InterruptionFlags.None; + if (_data.HasFlag(Data.FlagTypes.InterruptionRuleRechecking)) + flags |= InterruptionFlags.RuleRechecking; + if (_data.HasFlag(Data.FlagTypes.InterruptionInstant)) + flags |= InterruptionFlags.Instant; + return flags; + } + set + { + _data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking)); + _data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant)); + SourceState.SaveTransitions(true); + } + } + /// /// The rule graph data. /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 6b9a01474..ba303ab96 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -172,25 +172,12 @@ public: /// enum class FlagTypes { - /// - /// The none. - /// None = 0, - - /// - /// The enabled flag. - /// Enabled = 1, - - /// - /// The solo flag. - /// Solo = 2, - - /// - /// The use default rule flag. - /// UseDefaultRule = 4, + InterruptionRuleRechecking = 8, + InterruptionInstant = 16, }; public: diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 835128b93..ed75c2479 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1521,12 +1521,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // State Machine case 18: { + ANIM_GRAPH_PROFILE_EVENT("State Machine"); const int32 maxTransitionsPerUpdate = node->Values[2].AsInt; const bool reinitializeOnBecomingRelevant = node->Values[3].AsBool; const bool skipFirstUpdateTransition = node->Values[4].AsBool; - ANIM_GRAPH_PROFILE_EVENT("State Machine"); - // Prepare auto& bucket = context.Data->State[node->BucketIndex].StateMachine; auto& data = node->Data.StateMachine; @@ -1558,6 +1557,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Reset all state buckets pof the graphs and nodes included inside the state machine ResetBuckets(context, data.Graph); } +#define END_TRANSITION() \ + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \ + bucket.CurrentState = bucket.ActiveTransition->Destination; \ + bucket.ActiveTransition = nullptr; \ + bucket.TransitionPosition = 0.0f // Update the active transition if (bucket.ActiveTransition) @@ -1567,11 +1571,42 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Check for transition end if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration) { - // End transition - ResetBuckets(context, bucket.CurrentState->Data.State.Graph); - bucket.CurrentState = bucket.ActiveTransition->Destination; - bucket.ActiveTransition = nullptr; - bucket.TransitionPosition = 0.0f; + END_TRANSITION(); + } + // Check for transition interruption + else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking)) + { + const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule); + if (bucket.ActiveTransition->RuleGraph && !useDefaultRule) + { + // Execute transition rule + auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode(); + if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0])) + { + bool cancelTransition = false; + if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant)) + { + cancelTransition = true; + } + else + { + // Blend back to the source state (remove currently applied delta and rewind transition) + bucket.TransitionPosition -= context.DeltaTime; + bucket.TransitionPosition -= context.DeltaTime; + if (bucket.TransitionPosition <= ZeroTolerance) + { + cancelTransition = true; + } + } + if (cancelTransition) + { + // Go back to the source state + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); + bucket.ActiveTransition = nullptr; + bucket.TransitionPosition = 0.0f; + } + } + } } } @@ -1595,11 +1630,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Check for instant transitions if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance) { - // End transition - ResetBuckets(context, bucket.CurrentState->Data.State.Graph); - bucket.CurrentState = bucket.ActiveTransition->Destination; - bucket.ActiveTransition = nullptr; - bucket.TransitionPosition = 0.0f; + END_TRANSITION(); } } @@ -1620,6 +1651,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Update bucket bucket.LastUpdateFrame = context.CurrentFrameIndex; +#undef END_TRANSITION break; }