/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "LyShine_precompiled.h" #include "UiFlipbookAnimationComponent.h" #include #include #include #include #include #include #include #include #include #include #include namespace { const char* notConfiguredMessage = ""; //! Renames the float field "Frame Delay" to "Framerate" (as of V3). bool ConvertFrameDelayToFramerate( AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { int index = classElement.FindElement(AZ_CRC("Frame Delay")); if (index != -1) { AZ::SerializeContext::DataElementNode& frameDelayNode = classElement.GetSubElement(index); float frameDelayValue = 0; if (!frameDelayNode.GetData(frameDelayValue)) { AZ_Error("Serialization", false, "Element Frame Delay is not a float."); return false; } // remove the FrameDelay node classElement.RemoveElement(index); // If Framerate doesn't exist yet, add it index = classElement.FindElement(AZ_CRC("Framerate")); if (index == -1) { index = classElement.AddElement(context, "Framerate"); if (index == -1) { // Error adding the new sub element AZ_Error("Serialization", false, "Failed to create Framerate node"); return false; } } // Finally, set the framerate to be the same value as the frame delay AZ::SerializeContext::DataElementNode& framerateNode = classElement.GetSubElement(index); if (!framerateNode.SetData(context, frameDelayValue)) { AZ_Error("Serialization", false, "Unable to set Framerate to legacy Frame Delay value (%.2f).", frameDelayValue); return false; } } return true; } //! Convert legacy components to use seconds-per-frame as default time unit for playback. //! //! Prior to V3, default unit of time for playback was seconds-per-frame. bool ConvertFramerateUnitToSeconds( AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { // If Framerate Unit doesn't exist yet, add it int index = classElement.FindElement(AZ_CRC("Framerate Unit")); if (index == -1) { index = classElement.AddElement(context, "Framerate Unit"); if (index == -1) { // Error adding the new sub element AZ_Error("Serialization", false, "Failed to create Framerate Unit node"); return false; } } // Set the framerate unit to seconds for legacy reasons (FPS is default for newer versions of this component) AZ::SerializeContext::DataElementNode& framerateUnitNode = classElement.GetSubElement(index); const int secondsEnumVal = static_cast(UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame); if (!framerateUnitNode.SetData(context, secondsEnumVal)) { AZ_Error("Serialization", false, "Unable to set Framerate Unit to seconds (%d).", secondsEnumVal); return false; } return true; } } //////////////////////////////////////////////////////////////////////////////////////////////////// //! Forwards events to Lua for UiFlipbookAnimationNotificationsBus class UiFlipbookAnimationNotificationsBusBehaviorHandler : public UiFlipbookAnimationNotificationsBus::Handler , public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(UiFlipbookAnimationNotificationsBusBehaviorHandler, "{0A92A44E-0C32-4AD6-9C49-222A484B54FF}", AZ::SystemAllocator, OnAnimationStarted, OnAnimationStopped, OnLoopSequenceCompleted); void OnAnimationStarted() override { Call(FN_OnAnimationStarted); } void OnAnimationStopped() override { Call(FN_OnAnimationStopped); } void OnLoopSequenceCompleted() override { Call(FN_OnLoopSequenceCompleted); } }; //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(3, &VersionConverter) ->Field("Start Frame", &UiFlipbookAnimationComponent::m_startFrame) ->Field("End Frame", &UiFlipbookAnimationComponent::m_endFrame) ->Field("Loop Start Frame", &UiFlipbookAnimationComponent::m_loopStartFrame) ->Field("Loop Type", &UiFlipbookAnimationComponent::m_loopType) ->Field("Framerate Unit", &UiFlipbookAnimationComponent::m_framerateUnit) ->Field("Framerate", &UiFlipbookAnimationComponent::m_framerate) ->Field("Start Delay", &UiFlipbookAnimationComponent::m_startDelay) ->Field("Loop Delay", &UiFlipbookAnimationComponent::m_loopDelay) ->Field("Reverse Delay", &UiFlipbookAnimationComponent::m_reverseDelay) ->Field("Auto Play", &UiFlipbookAnimationComponent::m_isAutoPlay) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { auto editInfo = editContext->Class("FlipbookAnimation", "Animates image sequences or images configured as sprite sheets."); editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "UI") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Flipbook.png") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Flipbook.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI")) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_startFrame, "Start frame", "Frame to start at") ->Attribute("EnumValues", &UiFlipbookAnimationComponent::PopulateIndexStringList) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiFlipbookAnimationComponent::OnStartFrameChange) ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c)); ; editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_endFrame, "End frame", "Frame to end at") ->Attribute("EnumValues", &UiFlipbookAnimationComponent::PopulateIndexStringList) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiFlipbookAnimationComponent::OnEndFrameChange) ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c)); ; editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_loopStartFrame, "Loop start frame", "Frame to start looping from") ->Attribute("EnumValues", &UiFlipbookAnimationComponent::PopulateConstrainedIndexStringList) ; editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_loopType, "Loop type", "Go from start to end continuously or start to end and back to start") ->EnumAttribute(UiFlipbookAnimationInterface::LoopType::None, "None") ->EnumAttribute(UiFlipbookAnimationInterface::LoopType::Linear, "Linear") ->EnumAttribute(UiFlipbookAnimationInterface::LoopType::PingPong, "PingPong") ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c)) ; editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_framerateUnit, "Framerate unit", "Unit of measurement for framerate") ->EnumAttribute(UiFlipbookAnimationInterface::FramerateUnits::FPS, "FPS") ->EnumAttribute(UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame, "Seconds Per Frame") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiFlipbookAnimationComponent::OnFramerateUnitChange) ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c)) ; editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_framerate, "Framerate", "Determines transition speed between frames") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, AZ_FLT_MAX) ; editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_startDelay, "Start delay", "Number of seconds to wait before playing the flipbook (applied only once).") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, AZ_FLT_MAX) ; editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_loopDelay, "Loop delay", "Number of seconds to delay until the loop sequence plays") ->Attribute(AZ::Edit::Attributes::Visibility, &UiFlipbookAnimationComponent::IsLoopingType) ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, AZ_FLT_MAX) ; editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_reverseDelay, "Reverse delay", "Number of seconds to delay until the reverse sequence plays (PingPong loop types only)") ->Attribute(AZ::Edit::Attributes::Visibility, &UiFlipbookAnimationComponent::IsPingPongLoopType) ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, AZ_FLT_MAX) ; editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_isAutoPlay, "Auto Play", "Automatically starts playing the animation") ; } } AZ::BehaviorContext* behaviorContext = azrtti_cast(context); if (behaviorContext) { behaviorContext->EBus("UiFlipbookAnimationBus") ->Event("Start", &UiFlipbookAnimationBus::Events::Start) ->Event("Stop", &UiFlipbookAnimationBus::Events::Stop) ->Event("IsPlaying", &UiFlipbookAnimationBus::Events::IsPlaying) ->Event("GetStartFrame", &UiFlipbookAnimationBus::Events::GetStartFrame) ->Event("SetStartFrame", &UiFlipbookAnimationBus::Events::SetStartFrame) ->Event("GetEndFrame", &UiFlipbookAnimationBus::Events::GetEndFrame) ->Event("SetEndFrame", &UiFlipbookAnimationBus::Events::SetEndFrame) ->Event("GetCurrentFrame", &UiFlipbookAnimationBus::Events::GetCurrentFrame) ->Event("SetCurrentFrame", &UiFlipbookAnimationBus::Events::SetCurrentFrame) ->Event("GetLoopStartFrame", &UiFlipbookAnimationBus::Events::GetLoopStartFrame) ->Event("SetLoopStartFrame", &UiFlipbookAnimationBus::Events::SetLoopStartFrame) ->Event("GetLoopType", &UiFlipbookAnimationBus::Events::GetLoopType) ->Event("SetLoopType", &UiFlipbookAnimationBus::Events::SetLoopType) ->Event("GetFramerate", &UiFlipbookAnimationBus::Events::GetFramerate) ->Event("SetFramerate", &UiFlipbookAnimationBus::Events::SetFramerate) ->Event("GetFramerateUnit", &UiFlipbookAnimationBus::Events::GetFramerateUnit) ->Event("SetFramerateUnit", &UiFlipbookAnimationBus::Events::SetFramerateUnit) ->Event("GetStartDelay", &UiFlipbookAnimationBus::Events::GetStartDelay) ->Event("SetStartDelay", &UiFlipbookAnimationBus::Events::SetStartDelay) ->Event("GetLoopDelay", &UiFlipbookAnimationBus::Events::GetLoopDelay) ->Event("SetLoopDelay", &UiFlipbookAnimationBus::Events::SetLoopDelay) ->Event("GetReverseDelay", &UiFlipbookAnimationBus::Events::GetReverseDelay) ->Event("SetReverseDelay", &UiFlipbookAnimationBus::Events::SetReverseDelay) ->Event("GetIsAutoPlay", &UiFlipbookAnimationBus::Events::GetIsAutoPlay) ->Event("SetIsAutoPlay", &UiFlipbookAnimationBus::Events::SetIsAutoPlay) ; behaviorContext->EBus("UiFlipbookAnimationNotificationsBus") ->Handler() ; behaviorContext->Enum<(int)UiFlipbookAnimationInterface::LoopType::None>("eUiFlipbookAnimationLoopType_None") ->Enum<(int)UiFlipbookAnimationInterface::LoopType::Linear>("eUiFlipbookAnimationLoopType_Linear") ->Enum<(int)UiFlipbookAnimationInterface::LoopType::PingPong>("eUiFlipbookAnimationLoopType_PingPong") ; behaviorContext->Enum<(int)UiFlipbookAnimationInterface::FramerateUnits::FPS>("eUiFlipbookAnimationFramerateUnits_FPS") ->Enum<(int)UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame>("eUiFlipbookAnimationFramerateUnits_SecondsPerFrame") ; } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiFlipbookAnimationComponent::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { // conversion from version 2: // - Rename "frame delay" to "framerate" // - Set "framerate unit" to seconds (default moving forward is FPS, but we use seconds for legacy compatibility) if (classElement.GetVersion() <= 2) { if (!ConvertFrameDelayToFramerate(context, classElement)) { return false; } if (!ConvertFramerateUnitToSeconds(context, classElement)) { return false; } } return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// AZ::u32 UiFlipbookAnimationComponent::GetMaxFrame() const { AZ::u32 numImageIndices = 0; EBUS_EVENT_ID_RESULT(numImageIndices, GetEntityId(), UiIndexableImageBus, GetImageIndexCount); return numImageIndices; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiFlipbookAnimationComponent::FrameWithinRange(AZ::u32 frameValue) { AZ::u32 maxFrame = GetMaxFrame(); return maxFrame > 0 && frameValue < maxFrame; } //////////////////////////////////////////////////////////////////////////////////////////////////// LyShine::AZu32ComboBoxVec UiFlipbookAnimationComponent::PopulateIndexStringList() const { AZ::u32 numFrames = GetMaxFrame(); if (numFrames > 0) { return LyShine::GetEnumSpriteIndexList(GetEntityId(), 0, numFrames - 1); } // Add an empty element to prevent an AzToolsFramework warning that fires // when an empty container is encountered. LyShine::AZu32ComboBoxVec comboBoxVec; comboBoxVec.push_back(AZStd::make_pair(0, notConfiguredMessage)); return comboBoxVec; } //////////////////////////////////////////////////////////////////////////////////////////////////// LyShine::AZu32ComboBoxVec UiFlipbookAnimationComponent::PopulateConstrainedIndexStringList() const { const char* errorMessage = notConfiguredMessage; AZ::u32 indexCount = GetMaxFrame(); const bool isIndexedImage = indexCount > 1; if (isIndexedImage) { errorMessage = ""; } return LyShine::GetEnumSpriteIndexList(GetEntityId(), m_startFrame, m_endFrame, errorMessage); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::OnStartFrameChange() { m_endFrame = AZ::GetMax(m_startFrame, m_endFrame); m_currentFrame = AZ::GetClamp(m_currentFrame, m_startFrame, m_endFrame); m_loopStartFrame = AZ::GetClamp(m_loopStartFrame, m_startFrame, m_endFrame); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::OnEndFrameChange() { m_startFrame = AZ::GetMin(m_startFrame, m_endFrame); m_currentFrame = AZ::GetClamp(m_currentFrame, m_startFrame, m_endFrame); m_loopStartFrame = AZ::GetClamp(m_loopStartFrame, m_startFrame, m_endFrame); } void UiFlipbookAnimationComponent::OnFramerateUnitChange() { AZ_Assert(m_framerateUnit == FramerateUnits::FPS || m_framerateUnit == FramerateUnits::SecondsPerFrame, "New framerate unit added for flipbooks - please update this function accordingly!"); m_framerate = m_framerate != 0.0f ? 1.0f / m_framerate : 0.0f; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiFlipbookAnimationComponent::IsPingPongLoopType() const { return m_loopType == LoopType::PingPong; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiFlipbookAnimationComponent::IsLoopingType() const { return m_loopType != LoopType::None; } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiFlipbookAnimationComponent::CalculateLoopDelay() const { float loopDelay = 0.0f; if (IsLoopingType()) { const bool isStartFrame = m_currentFrame == m_loopStartFrame; const bool playingIntro = m_prevFrame < m_currentFrame && m_startFrame != m_loopStartFrame; const bool shouldApplyStartLoopDelay = isStartFrame && !playingIntro; if (shouldApplyStartLoopDelay) { loopDelay = m_loopDelay; } else if (m_loopType == LoopType::PingPong) { const bool isEndFrame = m_currentFrame == m_endFrame; const bool isPlayingReverse = m_currentLoopDirection < 0; const bool shouldApplyReverseDelay = isEndFrame && isPlayingReverse; if (shouldApplyReverseDelay) { loopDelay = m_reverseDelay; } } } return loopDelay; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::Activate() { UiFlipbookAnimationBus::Handler::BusConnect(GetEntityId()); UiInitializationBus::Handler::BusConnect(GetEntityId()); UiSpriteSourceNotificationBus::Handler::BusConnect(GetEntityId()); if (m_isPlaying) { // this is unlikely but possible. To get here a client would have to start the flipbook // playing and then deactivate and reactivate (e.g. add a component). AZ::EntityId canvasEntityId; EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); if (canvasEntityId.IsValid()) { UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::Deactivate() { UiFlipbookAnimationBus::Handler::BusDisconnect(); UiInitializationBus::Handler::BusDisconnect(); UiCanvasUpdateNotificationBus::Handler::BusDisconnect(); UiSpriteSourceNotificationBus::Handler::BusDisconnect(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::Update(float deltaTime) { if (m_isPlaying) { m_elapsedTime += deltaTime; if (m_useStartDelay) { if (m_elapsedTime >= m_startDelay) { m_useStartDelay = false; m_elapsedTime = 0.0f; EBUS_EVENT_ID(GetEntityId(), UiIndexableImageBus, SetImageIndex, m_currentFrame); } return; } const float loopDelay = CalculateLoopDelay(); // Calculate the frame delay (time to transition to next frame) based on framerate. // If framerate is in FPS we convert to seconds-per-frame to test against elapsedTime. const float frameDelay = CalculateFramerateAsSecondsPerFrame(); if (m_elapsedTime >= (frameDelay + loopDelay)) { // Determine the number of frames that has elapsed and adjust // "elapsed time" to account for any additional time that has // passed given the current delta. const float elapsedTimeAfterDelayFrame = m_elapsedTime - (frameDelay + loopDelay); const AZ::s32 numFramesElapsed = static_cast(1 + (elapsedTimeAfterDelayFrame / frameDelay)); m_elapsedTime = m_elapsedTime - ((numFramesElapsed * frameDelay) + loopDelay); // In case the loop direction is negative, we don't want to // subtract from the current frame if its zero. m_prevFrame = m_currentFrame; const AZ::s32 nextFrameNum = AZ::GetMax(0, static_cast(m_currentFrame) + numFramesElapsed * m_currentLoopDirection); m_currentFrame = static_cast(nextFrameNum); switch (m_loopType) { case LoopType::None: if (m_currentFrame > m_endFrame) { m_currentFrame = m_endFrame; Stop(); } break; case LoopType::Linear: if (m_currentFrame > m_endFrame) { m_currentFrame = m_loopStartFrame; EBUS_EVENT_ID(GetEntityId(), UiFlipbookAnimationNotificationsBus, OnLoopSequenceCompleted); } break; case LoopType::PingPong: if (m_currentLoopDirection > 0 && m_currentFrame >= m_endFrame) { m_currentLoopDirection = -1; m_currentFrame = m_endFrame; EBUS_EVENT_ID(GetEntityId(), UiFlipbookAnimationNotificationsBus, OnLoopSequenceCompleted); } else if (m_currentLoopDirection < 0 && m_currentFrame <= m_loopStartFrame) { m_currentLoopDirection = 1; m_currentFrame = m_loopStartFrame; EBUS_EVENT_ID(GetEntityId(), UiFlipbookAnimationNotificationsBus, OnLoopSequenceCompleted); } break; default: break; } // Show current frame EBUS_EVENT_ID(GetEntityId(), UiIndexableImageBus, SetImageIndex, m_currentFrame); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::InGamePostActivate() { if (m_isPlaying) { // Could get here if Start was called from Lua in the OnActivate function if (!UiCanvasUpdateNotificationBus::Handler::BusIsConnected()) { AZ::EntityId canvasEntityId; EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); if (canvasEntityId.IsValid()) { UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId); } } } else if (m_isAutoPlay) { Start(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::Start() { m_currentFrame = m_startFrame; m_currentLoopDirection = 1; m_isPlaying = true; m_elapsedTime = 0.0f; m_useStartDelay = m_startDelay > 0.0f ? true : false; // Show current frame if (!m_useStartDelay) { EBUS_EVENT_ID(GetEntityId(), UiIndexableImageBus, SetImageIndex, m_currentFrame); } // Start the update loop if (!UiCanvasUpdateNotificationBus::Handler::BusIsConnected()) { AZ::EntityId canvasEntityId; EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); // if this element has not been fixed up yet then canvasEntityId will be invalid. We handle this // in InGamePostActivate if (canvasEntityId.IsValid()) { UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId); } } // Let listeners know that we started playing EBUS_EVENT_ID(GetEntityId(), UiFlipbookAnimationNotificationsBus, OnAnimationStarted); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::Stop() { m_isPlaying = false; UiCanvasUpdateNotificationBus::Handler::BusDisconnect(); EBUS_EVENT_ID(GetEntityId(), UiFlipbookAnimationNotificationsBus, OnAnimationStopped); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::SetStartFrame(AZ::u32 startFrame) { if (!FrameWithinRange(startFrame)) { AZ_Warning("UI", false, "Invalid frame value given: %u", startFrame); return; } m_startFrame = startFrame; OnStartFrameChange(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::SetEndFrame(AZ::u32 endFrame) { if (!FrameWithinRange(endFrame)) { AZ_Warning("UI", false, "Invalid frame value given: %u", endFrame); return; } m_endFrame = endFrame; OnEndFrameChange(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::SetCurrentFrame(AZ::u32 currentFrame) { // The current frame needs to stay between the start and end frames const bool validFrameValue = currentFrame >= m_startFrame && currentFrame <= m_endFrame; if (!validFrameValue) { AZ_Warning("UI", false, "Invalid frame value given: %u", currentFrame); return; } m_currentFrame = currentFrame; EBUS_EVENT_ID(GetEntityId(), UiIndexableImageBus, SetImageIndex, m_currentFrame); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::SetLoopStartFrame(AZ::u32 loopStartFrame) { // Ensure that loop start frame exists within start and end frame range const bool validFrameValue = loopStartFrame >= m_startFrame && loopStartFrame <= m_endFrame; if (!validFrameValue) { AZ_Warning("UI", false, "Invalid frame value given: %u", loopStartFrame); return; } m_loopStartFrame = loopStartFrame; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::SetLoopType(UiFlipbookAnimationInterface::LoopType loopType) { m_loopType = loopType; // PingPong is currently the only loop type that supports a negative loop // direction. if (m_loopType != LoopType::PingPong) { m_currentLoopDirection = 1; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiFlipbookAnimationComponent::OnSpriteSourceChanged() { AZ::u32 indexCount = GetMaxFrame(); const AZ::u32 newStartFrame = AZ::GetClamp(m_startFrame, 0, indexCount - 1); const AZ::u32 newEndFrame = AZ::GetClamp(m_endFrame, 0, indexCount - 1); const bool frameRangesChanged = newStartFrame != m_startFrame || newEndFrame != m_endFrame; if (frameRangesChanged) { m_startFrame = newStartFrame; m_endFrame = newEndFrame; OnStartFrameChange(); OnEndFrameChange(); } }