Add **Animation Slot** node for playing animations from code in Anim Graph

This commit is contained in:
Wojtek Figat
2021-12-16 18:57:33 +01:00
parent 6f9f2ccdc0
commit 3c3f2ae075
7 changed files with 267 additions and 0 deletions

View File

@@ -953,6 +953,25 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 6, "Node:"),
}
},
new NodeArchetype
{
TypeID = 32,
Title = "Animation Slot",
Description = "Plays the animation from code with blending (eg. hit reaction).",
Flags = NodeFlags.AnimGraph,
Size = new Vector2(200, 40),
DefaultValues = new object[]
{
"Default",
},
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 1),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Slot:"),
NodeElementArchetype.Factory.TextBox(30, Surface.Constants.LayoutOffsetY, 140, TextBox.DefaultHeight, 0, false),
}
},
};
}
}

View File

@@ -105,6 +105,14 @@ void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
bucket.StateMachine.TransitionPosition = 0.0f;
}
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)
{
bucket.Slot.Index = -1;
bucket.Slot.TimePosition = 0.0f;
bucket.Slot.BlendInPosition = 0.0f;
bucket.Slot.BlendOutPosition = 0.0f;
}
bool SortMultiBlend1D(const byte& a, const byte& b, AnimGraphNode* n)
{
// Sort items by X location from the lowest to the highest
@@ -424,6 +432,12 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
}
break;
}
// Animation Slot
case 32:
{
ADD_BUCKET(SlotBucketInit);
break;
}
}
break;
// Custom

View File

@@ -90,6 +90,7 @@ void AnimGraphInstanceData::Clear()
Parameters.Resize(0);
State.Resize(0);
NodesPose.Resize(0);
Slots.Resize(0);
}
void AnimGraphInstanceData::ClearState()
@@ -101,6 +102,7 @@ void AnimGraphInstanceData::ClearState()
RootMotion = RootMotionData::Identity;
State.Resize(0);
NodesPose.Resize(0);
Slots.Clear();
}
void AnimGraphInstanceData::Invalidate()

View File

@@ -249,6 +249,19 @@ public:
}
};
/// <summary>
/// The animation graph slot-based animation.
/// </summary>
struct FLAXENGINE_API AnimGraphSlot
{
String Name;
AssetReference<Animation> Animation;
float Speed = 1.0f;
float BlendInTime = 0.0f;
float BlendOutTime = 0.0f;
bool Pause = false;
};
/// <summary>
/// The animation graph instance data storage. Required to update the animation graph.
/// </summary>
@@ -293,6 +306,14 @@ public:
float TransitionPosition;
};
struct SlotBucket
{
int32 Index;
float TimePosition;
float BlendInPosition;
float BlendOutPosition;
};
/// <summary>
/// The single data storage bucket for the instanced animation graph node. Used to store the node state (playback position, state, transition data).
/// </summary>
@@ -304,6 +325,7 @@ public:
MultiBlendBucket MultiBlend;
BlendPoseBucket BlendPose;
StateMachineBucket StateMachine;
SlotBucket Slot;
};
};
@@ -359,6 +381,11 @@ public:
/// </summary>
Delegate<AnimGraphImpulse*> LocalPoseOverride;
/// <summary>
/// The slots animations.
/// </summary>
Array<AnimGraphSlot> Slots;
public:
/// <summary>

View File

@@ -1817,6 +1817,80 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
value = nodes;
break;
}
// Animation Slot
case 32:
{
auto& slots = context.Data->Slots;
if (slots.Count() == 0)
{
value = tryGetValue(node->GetBox(1), Value::Null);
return;
}
const StringView slotName(node->Values[0]);
auto& bucket = context.Data->State[node->BucketIndex].Slot;
if (bucket.Index != -1 && (slots.Count() <= bucket.Index || slots[bucket.Index].Animation == nullptr))
{
// Current slot animation ended
bucket.Index = -1;
}
if (bucket.Index == -1)
{
// Pick the animation to play
for (int32 i = 0; i < slots.Count(); i++)
{
auto& slot = slots[i];
if (slot.Animation && slot.Name == slotName)
{
// Start playing animation
bucket.Index = i;
bucket.TimePosition = 0.0f;
bucket.BlendInPosition = 0.0f;
bucket.BlendOutPosition = 0.0f;
break;
}
}
if (bucket.Index == -1 || !slots[bucket.Index].Animation->IsLoaded())
{
value = tryGetValue(node->GetBox(1), Value::Null);
return;
}
}
// Play the animation
auto& slot = slots[bucket.Index];
Animation* anim = slot.Animation;
ASSERT(slot.Animation && slot.Animation->IsLoaded());
const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
const float length = anim->GetLength();
float newTimePos = bucket.TimePosition + deltaTime;
if (newTimePos >= length)
{
// End playing animation
value = tryGetValue(node->GetBox(1), Value::Null);
bucket.Index = -1;
slot.Animation = nullptr;
return;
}
value = SampleAnimation(node, false, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
bucket.TimePosition = newTimePos;
if (slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{
// Blend out
auto input = tryGetValue(node->GetBox(1), Value::Null);
bucket.BlendOutPosition += deltaTime;
const float alpha = Math::Saturate(bucket.BlendOutPosition / slot.BlendOutTime);
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
}
else if (slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
{
// Blend in
auto input = tryGetValue(node->GetBox(1), Value::Null);
bucket.BlendInPosition += deltaTime;
const float alpha = Math::Saturate(bucket.BlendInPosition / slot.BlendInTime);
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
}
break;
}
default:
break;
}

View File

@@ -324,6 +324,89 @@ void AnimatedModel::ClearBlendShapeWeights()
_blendShapes.Clear();
}
void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime)
{
CHECK(anim);
for (auto& slot : GraphInstance.Slots)
{
if (slot.Animation == anim && slot.Name == slotName)
{
slot.Pause = false;
slot.BlendInTime = blendInTime;
return;
}
}
int32 index = 0;
for (; index < GraphInstance.Slots.Count(); index++)
{
if (GraphInstance.Slots[index].Animation == nullptr)
break;
}
if (index == GraphInstance.Slots.Count())
GraphInstance.Slots.AddOne();
auto& slot = GraphInstance.Slots[index];
slot.Name = slotName;
slot.Animation = anim;
slot.Speed = speed;
slot.BlendInTime = blendInTime;
slot.BlendOutTime = blendOutTime;
}
void AnimatedModel::StopSlotAnimation()
{
GraphInstance.Slots.Clear();
}
void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* anim)
{
for (auto& slot : GraphInstance.Slots)
{
if (slot.Animation == anim && slot.Name == slotName)
{
slot.Animation = nullptr;
break;
}
}
}
void AnimatedModel::PauseSlotAnimation()
{
for (auto& slot : GraphInstance.Slots)
slot.Pause = true;
}
void AnimatedModel::PauseSlotAnimation(const StringView& slotName, Animation* anim)
{
for (auto& slot : GraphInstance.Slots)
{
if (slot.Animation == anim && slot.Name == slotName)
{
slot.Pause = true;
break;
}
}
}
bool AnimatedModel::IsPlayingSlotAnimation()
{
for (auto& slot : GraphInstance.Slots)
{
if (slot.Animation && !slot.Pause)
return true;
}
return false;
}
bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation* anim)
{
for (auto& slot : GraphInstance.Slots)
{
if (slot.Animation == anim && slot.Name == slotName && !slot.Pause)
return true;
}
return false;
}
void AnimatedModel::ApplyRootMotion(const RootMotionData& rootMotionDelta)
{
// Skip if no motion

View File

@@ -303,6 +303,54 @@ public:
/// </summary>
API_FUNCTION() void ClearBlendShapeWeights();
public:
/// <summary>
/// Plays the animation on the slot in Anim Graph.
/// </summary>
/// <param name="slotName">The name of the slot.</param>
/// <param name="anim">The animation to play.</param>
/// <param name="speed">The playback speed.</param>
/// <param name="blendInTime">The animation blending in time (in seconds). Cam be used to smooth the slot animation playback with the input pose when starting the animation.</param>
/// <param name="blendOutTime">The animation blending out time (in seconds). Cam be used to smooth the slot animation playback with the input pose when ending animation.</param>
API_FUNCTION() void PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed = 1.0f, float blendInTime = 0.2f, float blendOutTime = 0.2f);
/// <summary>
/// Stops all the animations playback on the all slots in Anim Graph.
/// </summary>
API_FUNCTION() void StopSlotAnimation();
/// <summary>
/// Stops the animation playback on the slot in Anim Graph.
/// </summary>
/// <param name="slotName">The name of the slot.</param>
/// <param name="anim">The animation to stop.</param>
API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim);
/// <summary>
/// Pauses all the animations playback on the all slots in Anim Graph.
/// </summary>
API_FUNCTION() void PauseSlotAnimation();
/// <summary>
/// Pauses the animation playback on the slot in Anim Graph.
/// </summary>
/// <param name="slotName">The name of the slot.</param>
/// <param name="anim">The animation to pause.</param>
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim);
/// <summary>
/// Checks if the any animation playback is active on the any slot in Anim Graph (not paused).
/// </summary>
API_FUNCTION() bool IsPlayingSlotAnimation();
/// <summary>
/// Checks if the animation playback is active on the slot in Anim Graph (not paused).
/// </summary>
/// <param name="slotName">The name of the slot.</param>
/// <param name="anim">The animation to check.</param>
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim);
private:
void ApplyRootMotion(const RootMotionData& rootMotionDelta);