diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 86efbb70c..725265359 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -111,6 +111,8 @@ void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket) bucket.Slot.TimePosition = 0.0f; bucket.Slot.BlendInPosition = 0.0f; bucket.Slot.BlendOutPosition = 0.0f; + bucket.Slot.LoopsDone = 0; + bucket.Slot.LoopsLeft = 0; } bool SortMultiBlend1D(const byte& a, const byte& b, AnimGraphNode* n) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index dde47b8b6..70c3d9696 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -260,6 +260,7 @@ struct FLAXENGINE_API AnimGraphSlot float Speed = 1.0f; float BlendInTime = 0.0f; float BlendOutTime = 0.0f; + int32 LoopCount = 0; bool Pause = false; }; @@ -314,6 +315,8 @@ public: float TimePosition; float BlendInPosition; float BlendOutPosition; + int32 LoopsDone; + int32 LoopsLeft; }; /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index e34450a17..0f4a206e5 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1924,6 +1924,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.TimePosition = 0.0f; bucket.BlendInPosition = 0.0f; bucket.BlendOutPosition = 0.0f; + bucket.LoopsDone = 0; + bucket.LoopsLeft = slot.LoopCount; break; } } @@ -1940,18 +1942,27 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu ASSERT(slot.Animation && slot.Animation->IsLoaded()); const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed; const float length = anim->GetLength(); + const bool loop = bucket.LoopsLeft != 0; 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; + if (bucket.LoopsLeft == 0) + { + // End playing animation + value = tryGetValue(node->GetBox(1), Value::Null); + bucket.Index = -1; + slot.Animation = nullptr; + return; + } + + // Loop animation + if (bucket.LoopsLeft > 0) + bucket.LoopsLeft--; + bucket.LoopsDone++; } - value = SampleAnimation(node, false, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed); + value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed); bucket.TimePosition = newTimePos; - if (slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition) + if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition) { // Blend out auto input = tryGetValue(node->GetBox(1), Value::Null); @@ -1959,7 +1970,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu 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) + else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime) { // Blend in auto input = tryGetValue(node->GetBox(1), Value::Null); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 87226ac9c..44b50ac3a 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -325,7 +325,7 @@ void AnimatedModel::ClearBlendShapeWeights() _blendShapes.Clear(); } -void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime) +void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime, int32 loopCount) { CHECK(anim); for (auto& slot : GraphInstance.Slots) @@ -334,6 +334,7 @@ void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* ani { slot.Pause = false; slot.BlendInTime = blendInTime; + slot.LoopCount = loopCount; return; } } @@ -351,6 +352,7 @@ void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* ani slot.Speed = speed; slot.BlendInTime = blendInTime; slot.BlendOutTime = blendOutTime; + slot.LoopCount = loopCount; } void AnimatedModel::StopSlotAnimation() diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 556a1d1cf..d8491e78a 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -313,7 +313,8 @@ public: /// 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); + /// The amount of loops to play the animation: 0 to play once, -1 to play infinite, 1 or higher to loop once or more. + API_FUNCTION() void PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed = 1.0f, float blendInTime = 0.2f, float blendOutTime = 0.2f, int32 loopCount = 0); /// /// Stops all the animations playback on the all slots in Anim Graph.