From 3c3f2ae0752292cf652766c6bab755f8e5838068 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 16 Dec 2021 18:57:33 +0100 Subject: [PATCH] Add **Animation Slot** node for playing animations from code in Anim Graph --- Source/Editor/Surface/Archetypes/Animation.cs | 19 +++++ .../Animations/Graph/AnimGraph.Base.cpp | 14 ++++ Source/Engine/Animations/Graph/AnimGraph.cpp | 2 + Source/Engine/Animations/Graph/AnimGraph.h | 27 ++++++ .../Animations/Graph/AnimGroup.Animation.cpp | 74 +++++++++++++++++ Source/Engine/Level/Actors/AnimatedModel.cpp | 83 +++++++++++++++++++ Source/Engine/Level/Actors/AnimatedModel.h | 48 +++++++++++ 7 files changed, 267 insertions(+) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 15096e696..0f73d67d6 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -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), + } + }, }; } } diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index facf78b43..5dd7eaa3b 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -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 diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 53af88ba5..6d702350d 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -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() diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 0136e35df..88c7a2eec 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -249,6 +249,19 @@ public: } }; +/// +/// The animation graph slot-based animation. +/// +struct FLAXENGINE_API AnimGraphSlot +{ + String Name; + AssetReference Animation; + float Speed = 1.0f; + float BlendInTime = 0.0f; + float BlendOutTime = 0.0f; + bool Pause = false; +}; + /// /// The animation graph instance data storage. Required to update the animation graph. /// @@ -293,6 +306,14 @@ public: float TransitionPosition; }; + struct SlotBucket + { + int32 Index; + float TimePosition; + float BlendInPosition; + float BlendOutPosition; + }; + /// /// The single data storage bucket for the instanced animation graph node. Used to store the node state (playback position, state, transition data). /// @@ -304,6 +325,7 @@ public: MultiBlendBucket MultiBlend; BlendPoseBucket BlendPose; StateMachineBucket StateMachine; + SlotBucket Slot; }; }; @@ -359,6 +381,11 @@ public: /// Delegate LocalPoseOverride; + /// + /// The slots animations. + /// + Array Slots; + public: /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index e02b3eb4a..080109a12 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -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; } diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 39496156f..9fc6672d1 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -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 diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index bb42913b8..f3eb2500b 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -303,6 +303,54 @@ public: /// API_FUNCTION() void ClearBlendShapeWeights(); +public: + + /// + /// Plays the animation on the slot in Anim Graph. + /// + /// The name of the slot. + /// The animation to play. + /// The playback speed. + /// The animation blending in time (in seconds). Cam be used to smooth the slot animation playback with the input pose when starting the animation. + /// The animation blending out time (in seconds). Cam be used to smooth the slot animation playback with the input pose when ending animation. + API_FUNCTION() void PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed = 1.0f, float blendInTime = 0.2f, float blendOutTime = 0.2f); + + /// + /// Stops all the animations playback on the all slots in Anim Graph. + /// + API_FUNCTION() void StopSlotAnimation(); + + /// + /// Stops the animation playback on the slot in Anim Graph. + /// + /// The name of the slot. + /// The animation to stop. + API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim); + + /// + /// Pauses all the animations playback on the all slots in Anim Graph. + /// + API_FUNCTION() void PauseSlotAnimation(); + + /// + /// Pauses the animation playback on the slot in Anim Graph. + /// + /// The name of the slot. + /// The animation to pause. + API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim); + + /// + /// Checks if the any animation playback is active on the any slot in Anim Graph (not paused). + /// + API_FUNCTION() bool IsPlayingSlotAnimation(); + + /// + /// Checks if the animation playback is active on the slot in Anim Graph (not paused). + /// + /// The name of the slot. + /// The animation to check. + API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim); + private: void ApplyRootMotion(const RootMotionData& rootMotionDelta);