You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/LyShine/Code/Source/UiFlipbookAnimationComponen...

664 lines
28 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "UiFlipbookAnimationComponent.h"
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <LyShine/Bus/UiGameEntityContextBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiImageBus.h>
#include <LyShine/Bus/UiIndexableImageBus.h>
#include <LyShine/UiSerializeHelpers.h>
namespace
{
const char* notConfiguredMessage = "<Spritesheet/image index unavailable>";
//! 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<float>(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<float>(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<float>(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<int>(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<int>(UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame);
if (!framerateUnitNode.SetData<int>(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);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
static bool UiFlipbookAnimationComponentVersionConverter(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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiFlipbookAnimationComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<UiFlipbookAnimationComponent, AZ::Component>()
->Version(3, &UiFlipbookAnimationComponentVersionConverter)
->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<UiFlipbookAnimationComponent>("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, "Icons/Components/Flipbook.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Flipbook.svg")
->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::Constants::FloatMax)
;
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::Constants::FloatMax)
;
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::Constants::FloatMax)
;
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::Constants::FloatMax)
;
editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_isAutoPlay, "Auto Play", "Automatically starts playing the animation")
;
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<UiFlipbookAnimationBus>("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>("UiFlipbookAnimationNotificationsBus")
->Handler<UiFlipbookAnimationNotificationsBusBehaviorHandler>()
;
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")
;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
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 = "<Invalid loop range>";
}
return LyShine::GetEnumSpriteIndexList(GetEntityId(), m_startFrame, m_endFrame, errorMessage);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiFlipbookAnimationComponent::OnStartFrameChange()
{
m_endFrame = AZ::GetMax<AZ::u32>(m_startFrame, m_endFrame);
m_currentFrame = AZ::GetClamp<AZ::u32>(m_currentFrame, m_startFrame, m_endFrame);
m_loopStartFrame = AZ::GetClamp<AZ::u32>(m_loopStartFrame, m_startFrame, m_endFrame);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiFlipbookAnimationComponent::OnEndFrameChange()
{
m_startFrame = AZ::GetMin<AZ::u32>(m_startFrame, m_endFrame);
m_currentFrame = AZ::GetClamp<AZ::u32>(m_currentFrame, m_startFrame, m_endFrame);
m_loopStartFrame = AZ::GetClamp<AZ::u32>(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<AZ::s32>(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<AZ::s32>(0, static_cast<AZ::s32>(m_currentFrame) + numFramesElapsed * m_currentLoopDirection);
m_currentFrame = static_cast<AZ::u32>(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<AZ::u32>(m_startFrame, 0, indexCount - 1);
const AZ::u32 newEndFrame = AZ::GetClamp<AZ::u32>(m_endFrame, 0, indexCount - 1);
const bool frameRangesChanged = newStartFrame != m_startFrame || newEndFrame != m_endFrame;
if (frameRangesChanged)
{
m_startFrame = newStartFrame;
m_endFrame = newEndFrame;
OnStartFrameChange();
OnEndFrameChange();
}
}