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;
}