Add **Interruption options** to State Machine transition

This commit is contained in:
Wojtek Figat
2023-04-25 10:47:10 +02:00
parent 7158cfb5bf
commit 698a9f1938
3 changed files with 103 additions and 64 deletions

View File

@@ -1552,7 +1552,7 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{ {
} }
/// <inheritdoc /> /// <inheritdoc />
public override int TransitionsDataIndex => 0; public override int TransitionsDataIndex => 0;
} }
@@ -1564,6 +1564,28 @@ namespace FlaxEditor.Surface.Archetypes
/// <seealso cref="ISurfaceContext"/> /// <seealso cref="ISurfaceContext"/>
internal class StateMachineTransition : ISurfaceContext internal class StateMachineTransition : ISurfaceContext
{ {
/// <summary>
/// State transition interruption flags.
/// </summary>
[Flags]
public enum InterruptionFlags
{
/// <summary>
/// Nothing.
/// </summary>
None = 0,
/// <summary>
/// Transition rule will be rechecked during active transition with option to interrupt transition.
/// </summary>
RuleRechecking = 1,
/// <summary>
/// Interrupted transition is immediately stopped without blending out.
/// </summary>
Instant = 2,
}
/// <summary> /// <summary>
/// The packed data container for the transition data storage. Helps with serialization and versioning the data. /// The packed data container for the transition data storage. Helps with serialization and versioning the data.
/// Must match AnimGraphBase::LoadStateTransition in C++ /// Must match AnimGraphBase::LoadStateTransition in C++
@@ -1574,31 +1596,16 @@ namespace FlaxEditor.Surface.Archetypes
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 32)] [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 32)]
internal struct Data internal struct Data
{ {
/// <summary> // Must match AnimGraphStateTransition::FlagTypes
/// The transition flag types.
/// </summary>
[Flags] [Flags]
public enum FlagTypes public enum FlagTypes
{ {
/// <summary>
/// The none.
/// </summary>
None = 0, None = 0,
/// <summary>
/// The enabled flag.
/// </summary>
Enabled = 1, Enabled = 1,
/// <summary>
/// The solo flag.
/// </summary>
Solo = 2, Solo = 2,
/// <summary>
/// The use default rule flag.
/// </summary>
UseDefaultRule = 4, UseDefaultRule = 4,
InterruptionRuleRechecking = 8,
InterruptionInstant = 16,
} }
/// <summary> /// <summary>
@@ -1641,21 +1648,11 @@ namespace FlaxEditor.Surface.Archetypes
/// </summary> /// </summary>
public int Unused2; public int Unused2;
/// <summary>
/// Determines whether the data has a given flag set.
/// </summary>
/// <param name="flag">The flag.</param>
/// <returns><c>true</c> if the specified flag is set; otherwise, <c>false</c>.</returns>
public bool HasFlag(FlagTypes flag) public bool HasFlag(FlagTypes flag)
{ {
return (Flags & flag) == flag; return (Flags & flag) == flag;
} }
/// <summary>
/// Sets the flag to the given value.
/// </summary>
/// <param name="flag">The flag.</param>
/// <param name="value">If set to <c>true</c> the flag will be set, otherwise it will be cleared.</param>
public void SetFlag(FlagTypes flag, bool value) public void SetFlag(FlagTypes flag, bool value)
{ {
if (value) if (value)
@@ -1683,7 +1680,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary> /// <summary>
/// If checked, the transition can be triggered, otherwise it will be ignored. /// If checked, the transition can be triggered, otherwise it will be ignored.
/// </summary> /// </summary>
[EditorOrder(10), DefaultValue(true), Tooltip("If checked, the transition can be triggered, otherwise it will be ignored.")] [EditorOrder(10), DefaultValue(true)]
public bool Enabled public bool Enabled
{ {
get => _data.HasFlag(Data.FlagTypes.Enabled); get => _data.HasFlag(Data.FlagTypes.Enabled);
@@ -1698,7 +1695,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary> /// <summary>
/// If checked, animation graph will ignore other transitions from the source state and use only this transition. /// If checked, animation graph will ignore other transitions from the source state and use only this transition.
/// </summary> /// </summary>
[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 public bool Solo
{ {
get => _data.HasFlag(Data.FlagTypes.Solo); get => _data.HasFlag(Data.FlagTypes.Solo);
@@ -1713,7 +1710,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary> /// <summary>
/// If checked, animation graph will perform automatic transition based on the state animation pose (single shot animation play). /// If checked, animation graph will perform automatic transition based on the state animation pose (single shot animation play).
/// </summary> /// </summary>
[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 public bool UseDefaultRule
{ {
get => _data.HasFlag(Data.FlagTypes.UseDefaultRule); get => _data.HasFlag(Data.FlagTypes.UseDefaultRule);
@@ -1725,9 +1722,9 @@ namespace FlaxEditor.Surface.Archetypes
} }
/// <summary> /// <summary>
/// The transition order (higher first). /// The transition order. Transitions with the higher order are handled before the ones with the lower order.
/// </summary> /// </summary>
[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 public int Order
{ {
get => _data.Order; get => _data.Order;
@@ -1743,7 +1740,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <summary> /// <summary>
/// The blend duration (in seconds). /// The blend duration (in seconds).
/// </summary> /// </summary>
[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 public float BlendDuration
{ {
get => _data.BlendDuration; get => _data.BlendDuration;
@@ -1755,9 +1752,9 @@ namespace FlaxEditor.Surface.Archetypes
} }
/// <summary> /// <summary>
/// The blend mode. /// Transition blending mode for blend alpha.
/// </summary> /// </summary>
[EditorOrder(60), DefaultValue(AlphaBlendMode.HermiteCubic), Tooltip("Transition blending mode for blend alpha.")] [EditorOrder(60), DefaultValue(AlphaBlendMode.HermiteCubic)]
public AlphaBlendMode BlendMode public AlphaBlendMode BlendMode
{ {
get => _data.BlendMode; get => _data.BlendMode;
@@ -1768,6 +1765,29 @@ namespace FlaxEditor.Surface.Archetypes
} }
} }
/// <summary>
/// Transition interruption options.
/// </summary>
[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);
}
}
/// <summary> /// <summary>
/// The rule graph data. /// The rule graph data.
/// </summary> /// </summary>

View File

@@ -172,25 +172,12 @@ public:
/// </summary> /// </summary>
enum class FlagTypes enum class FlagTypes
{ {
/// <summary>
/// The none.
/// </summary>
None = 0, None = 0,
/// <summary>
/// The enabled flag.
/// </summary>
Enabled = 1, Enabled = 1,
/// <summary>
/// The solo flag.
/// </summary>
Solo = 2, Solo = 2,
/// <summary>
/// The use default rule flag.
/// </summary>
UseDefaultRule = 4, UseDefaultRule = 4,
InterruptionRuleRechecking = 8,
InterruptionInstant = 16,
}; };
public: public:

View File

@@ -1521,12 +1521,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// State Machine // State Machine
case 18: case 18:
{ {
ANIM_GRAPH_PROFILE_EVENT("State Machine");
const int32 maxTransitionsPerUpdate = node->Values[2].AsInt; const int32 maxTransitionsPerUpdate = node->Values[2].AsInt;
const bool reinitializeOnBecomingRelevant = node->Values[3].AsBool; const bool reinitializeOnBecomingRelevant = node->Values[3].AsBool;
const bool skipFirstUpdateTransition = node->Values[4].AsBool; const bool skipFirstUpdateTransition = node->Values[4].AsBool;
ANIM_GRAPH_PROFILE_EVENT("State Machine");
// Prepare // Prepare
auto& bucket = context.Data->State[node->BucketIndex].StateMachine; auto& bucket = context.Data->State[node->BucketIndex].StateMachine;
auto& data = node->Data.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 // Reset all state buckets pof the graphs and nodes included inside the state machine
ResetBuckets(context, data.Graph); 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 // Update the active transition
if (bucket.ActiveTransition) if (bucket.ActiveTransition)
@@ -1567,11 +1571,42 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Check for transition end // Check for transition end
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration) if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
{ {
// End transition END_TRANSITION();
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); }
bucket.CurrentState = bucket.ActiveTransition->Destination; // Check for transition interruption
bucket.ActiveTransition = nullptr; else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
bucket.TransitionPosition = 0.0f; {
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 // Check for instant transitions
if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance) if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance)
{ {
// End transition END_TRANSITION();
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
bucket.CurrentState = bucket.ActiveTransition->Destination;
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
} }
} }
@@ -1620,6 +1651,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Update bucket // Update bucket
bucket.LastUpdateFrame = context.CurrentFrameIndex; bucket.LastUpdateFrame = context.CurrentFrameIndex;
#undef END_TRANSITION
break; break;
} }