Add **Animation Slot** node for playing animations from code in Anim Graph
This commit is contained in:
@@ -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),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user