UiFlipbookAnimationComponent.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "UiFlipbookAnimationComponent.h"
  9. #include <AzCore/Serialization/EditContext.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/Component/ComponentApplicationBus.h>
  12. #include <AzCore/Component/Entity.h>
  13. #include <AzCore/RTTI/BehaviorContext.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. #include <LyShine/Bus/UiGameEntityContextBus.h>
  16. #include <LyShine/Bus/UiElementBus.h>
  17. #include <LyShine/Bus/UiImageBus.h>
  18. #include <LyShine/Bus/UiIndexableImageBus.h>
  19. #include <LyShine/UiSerializeHelpers.h>
  20. namespace
  21. {
  22. const char* notConfiguredMessage = "<Spritesheet/image index unavailable>";
  23. //! Renames the float field "Frame Delay" to "Framerate" (as of V3).
  24. bool ConvertFrameDelayToFramerate(
  25. AZ::SerializeContext& context,
  26. AZ::SerializeContext::DataElementNode& classElement)
  27. {
  28. int index = classElement.FindElement(AZ_CRC_CE("Frame Delay"));
  29. if (index != -1)
  30. {
  31. AZ::SerializeContext::DataElementNode& frameDelayNode = classElement.GetSubElement(index);
  32. float frameDelayValue = 0;
  33. if (!frameDelayNode.GetData<float>(frameDelayValue))
  34. {
  35. AZ_Error("Serialization", false, "Element Frame Delay is not a float.");
  36. return false;
  37. }
  38. // remove the FrameDelay node
  39. classElement.RemoveElement(index);
  40. // If Framerate doesn't exist yet, add it
  41. index = classElement.FindElement(AZ_CRC_CE("Framerate"));
  42. if (index == -1)
  43. {
  44. index = classElement.AddElement<float>(context, "Framerate");
  45. if (index == -1)
  46. {
  47. // Error adding the new sub element
  48. AZ_Error("Serialization", false, "Failed to create Framerate node");
  49. return false;
  50. }
  51. }
  52. // Finally, set the framerate to be the same value as the frame delay
  53. AZ::SerializeContext::DataElementNode& framerateNode = classElement.GetSubElement(index);
  54. if (!framerateNode.SetData<float>(context, frameDelayValue))
  55. {
  56. AZ_Error("Serialization", false, "Unable to set Framerate to legacy Frame Delay value (%.2f).", frameDelayValue);
  57. return false;
  58. }
  59. }
  60. return true;
  61. }
  62. //! Convert legacy components to use seconds-per-frame as default time unit for playback.
  63. //!
  64. //! Prior to V3, default unit of time for playback was seconds-per-frame.
  65. bool ConvertFramerateUnitToSeconds(
  66. AZ::SerializeContext& context,
  67. AZ::SerializeContext::DataElementNode& classElement)
  68. {
  69. // If Framerate Unit doesn't exist yet, add it
  70. int index = classElement.FindElement(AZ_CRC_CE("Framerate Unit"));
  71. if (index == -1)
  72. {
  73. index = classElement.AddElement<int>(context, "Framerate Unit");
  74. if (index == -1)
  75. {
  76. // Error adding the new sub element
  77. AZ_Error("Serialization", false, "Failed to create Framerate Unit node");
  78. return false;
  79. }
  80. }
  81. // Set the framerate unit to seconds for legacy reasons (FPS is default for newer versions of this component)
  82. AZ::SerializeContext::DataElementNode& framerateUnitNode = classElement.GetSubElement(index);
  83. const int secondsEnumVal = static_cast<int>(UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame);
  84. if (!framerateUnitNode.SetData<int>(context, secondsEnumVal))
  85. {
  86. AZ_Error("Serialization", false, "Unable to set Framerate Unit to seconds (%d).", secondsEnumVal);
  87. return false;
  88. }
  89. return true;
  90. }
  91. }
  92. ////////////////////////////////////////////////////////////////////////////////////////////////////
  93. //! Forwards events to Lua for UiFlipbookAnimationNotificationsBus
  94. class UiFlipbookAnimationNotificationsBusBehaviorHandler
  95. : public UiFlipbookAnimationNotificationsBus::Handler
  96. , public AZ::BehaviorEBusHandler
  97. {
  98. public:
  99. AZ_EBUS_BEHAVIOR_BINDER(UiFlipbookAnimationNotificationsBusBehaviorHandler, "{0A92A44E-0C32-4AD6-9C49-222A484B54FF}", AZ::SystemAllocator,
  100. OnAnimationStarted, OnAnimationStopped, OnLoopSequenceCompleted);
  101. void OnAnimationStarted() override
  102. {
  103. Call(FN_OnAnimationStarted);
  104. }
  105. void OnAnimationStopped() override
  106. {
  107. Call(FN_OnAnimationStopped);
  108. }
  109. void OnLoopSequenceCompleted() override
  110. {
  111. Call(FN_OnLoopSequenceCompleted);
  112. }
  113. };
  114. ////////////////////////////////////////////////////////////////////////////////////////////////////
  115. static bool UiFlipbookAnimationComponentVersionConverter(AZ::SerializeContext& context,
  116. AZ::SerializeContext::DataElementNode& classElement)
  117. {
  118. // conversion from version 2:
  119. // - Rename "frame delay" to "framerate"
  120. // - Set "framerate unit" to seconds (default moving forward is FPS, but we use seconds for legacy compatibility)
  121. if (classElement.GetVersion() <= 2)
  122. {
  123. if (!ConvertFrameDelayToFramerate(context, classElement))
  124. {
  125. return false;
  126. }
  127. if (!ConvertFramerateUnitToSeconds(context, classElement))
  128. {
  129. return false;
  130. }
  131. }
  132. return true;
  133. }
  134. ////////////////////////////////////////////////////////////////////////////////////////////////////
  135. void UiFlipbookAnimationComponent::Reflect(AZ::ReflectContext* context)
  136. {
  137. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  138. if (serializeContext)
  139. {
  140. serializeContext->Class<UiFlipbookAnimationComponent, AZ::Component>()
  141. ->Version(3, &UiFlipbookAnimationComponentVersionConverter)
  142. ->Field("Start Frame", &UiFlipbookAnimationComponent::m_startFrame)
  143. ->Field("End Frame", &UiFlipbookAnimationComponent::m_endFrame)
  144. ->Field("Loop Start Frame", &UiFlipbookAnimationComponent::m_loopStartFrame)
  145. ->Field("Loop Type", &UiFlipbookAnimationComponent::m_loopType)
  146. ->Field("Framerate Unit", &UiFlipbookAnimationComponent::m_framerateUnit)
  147. ->Field("Framerate", &UiFlipbookAnimationComponent::m_framerate)
  148. ->Field("Start Delay", &UiFlipbookAnimationComponent::m_startDelay)
  149. ->Field("Loop Delay", &UiFlipbookAnimationComponent::m_loopDelay)
  150. ->Field("Reverse Delay", &UiFlipbookAnimationComponent::m_reverseDelay)
  151. ->Field("Auto Play", &UiFlipbookAnimationComponent::m_isAutoPlay)
  152. ;
  153. AZ::EditContext* editContext = serializeContext->GetEditContext();
  154. if (editContext)
  155. {
  156. auto editInfo = editContext->Class<UiFlipbookAnimationComponent>("FlipbookAnimation",
  157. "Animates image sequences or images configured as sprite sheets.");
  158. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  159. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  160. ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Flipbook.png")
  161. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Flipbook.svg")
  162. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("UI"))
  163. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  164. ;
  165. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_startFrame, "Start frame", "Frame to start at")
  166. ->Attribute("EnumValues", &UiFlipbookAnimationComponent::PopulateIndexStringList)
  167. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiFlipbookAnimationComponent::OnStartFrameChange)
  168. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC_CE("RefreshEntireTree"));
  169. ;
  170. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_endFrame, "End frame", "Frame to end at")
  171. ->Attribute("EnumValues", &UiFlipbookAnimationComponent::PopulateIndexStringList)
  172. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiFlipbookAnimationComponent::OnEndFrameChange)
  173. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC_CE("RefreshEntireTree"));
  174. ;
  175. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_loopStartFrame, "Loop start frame", "Frame to start looping from")
  176. ->Attribute("EnumValues", &UiFlipbookAnimationComponent::PopulateConstrainedIndexStringList)
  177. ;
  178. 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")
  179. ->EnumAttribute(UiFlipbookAnimationInterface::LoopType::None, "None")
  180. ->EnumAttribute(UiFlipbookAnimationInterface::LoopType::Linear, "Linear")
  181. ->EnumAttribute(UiFlipbookAnimationInterface::LoopType::PingPong, "PingPong")
  182. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC_CE("RefreshEntireTree"))
  183. ;
  184. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiFlipbookAnimationComponent::m_framerateUnit, "Framerate unit", "Unit of measurement for framerate")
  185. ->EnumAttribute(UiFlipbookAnimationInterface::FramerateUnits::FPS, "FPS")
  186. ->EnumAttribute(UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame, "Seconds Per Frame")
  187. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiFlipbookAnimationComponent::OnFramerateUnitChange)
  188. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC_CE("RefreshEntireTree"))
  189. ;
  190. editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_framerate, "Framerate", "Determines transition speed between frames")
  191. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  192. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::FloatMax)
  193. ;
  194. editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_startDelay, "Start delay", "Number of seconds to wait before playing the flipbook (applied only once).")
  195. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  196. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::FloatMax)
  197. ;
  198. editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_loopDelay, "Loop delay", "Number of seconds to delay until the loop sequence plays")
  199. ->Attribute(AZ::Edit::Attributes::Visibility, &UiFlipbookAnimationComponent::IsLoopingType)
  200. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  201. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::FloatMax)
  202. ;
  203. editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_reverseDelay, "Reverse delay", "Number of seconds to delay until the reverse sequence plays (PingPong loop types only)")
  204. ->Attribute(AZ::Edit::Attributes::Visibility, &UiFlipbookAnimationComponent::IsPingPongLoopType)
  205. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  206. ->Attribute(AZ::Edit::Attributes::Max, AZ::Constants::FloatMax)
  207. ;
  208. editInfo->DataElement(0, &UiFlipbookAnimationComponent::m_isAutoPlay, "Auto Play", "Automatically starts playing the animation")
  209. ;
  210. }
  211. }
  212. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  213. if (behaviorContext)
  214. {
  215. behaviorContext->EBus<UiFlipbookAnimationBus>("UiFlipbookAnimationBus")
  216. ->Event("Start", &UiFlipbookAnimationBus::Events::Start)
  217. ->Event("Stop", &UiFlipbookAnimationBus::Events::Stop)
  218. ->Event("IsPlaying", &UiFlipbookAnimationBus::Events::IsPlaying)
  219. ->Event("GetStartFrame", &UiFlipbookAnimationBus::Events::GetStartFrame)
  220. ->Event("SetStartFrame", &UiFlipbookAnimationBus::Events::SetStartFrame)
  221. ->Event("GetEndFrame", &UiFlipbookAnimationBus::Events::GetEndFrame)
  222. ->Event("SetEndFrame", &UiFlipbookAnimationBus::Events::SetEndFrame)
  223. ->Event("GetCurrentFrame", &UiFlipbookAnimationBus::Events::GetCurrentFrame)
  224. ->Event("SetCurrentFrame", &UiFlipbookAnimationBus::Events::SetCurrentFrame)
  225. ->Event("GetLoopStartFrame", &UiFlipbookAnimationBus::Events::GetLoopStartFrame)
  226. ->Event("SetLoopStartFrame", &UiFlipbookAnimationBus::Events::SetLoopStartFrame)
  227. ->Event("GetLoopType", &UiFlipbookAnimationBus::Events::GetLoopType)
  228. ->Event("SetLoopType", &UiFlipbookAnimationBus::Events::SetLoopType)
  229. ->Event("GetFramerate", &UiFlipbookAnimationBus::Events::GetFramerate)
  230. ->Event("SetFramerate", &UiFlipbookAnimationBus::Events::SetFramerate)
  231. ->Event("GetFramerateUnit", &UiFlipbookAnimationBus::Events::GetFramerateUnit)
  232. ->Event("SetFramerateUnit", &UiFlipbookAnimationBus::Events::SetFramerateUnit)
  233. ->Event("GetStartDelay", &UiFlipbookAnimationBus::Events::GetStartDelay)
  234. ->Event("SetStartDelay", &UiFlipbookAnimationBus::Events::SetStartDelay)
  235. ->Event("GetLoopDelay", &UiFlipbookAnimationBus::Events::GetLoopDelay)
  236. ->Event("SetLoopDelay", &UiFlipbookAnimationBus::Events::SetLoopDelay)
  237. ->Event("GetReverseDelay", &UiFlipbookAnimationBus::Events::GetReverseDelay)
  238. ->Event("SetReverseDelay", &UiFlipbookAnimationBus::Events::SetReverseDelay)
  239. ->Event("GetIsAutoPlay", &UiFlipbookAnimationBus::Events::GetIsAutoPlay)
  240. ->Event("SetIsAutoPlay", &UiFlipbookAnimationBus::Events::SetIsAutoPlay)
  241. ;
  242. behaviorContext->EBus<UiFlipbookAnimationNotificationsBus>("UiFlipbookAnimationNotificationsBus")
  243. ->Handler<UiFlipbookAnimationNotificationsBusBehaviorHandler>()
  244. ;
  245. behaviorContext->Enum<(int)UiFlipbookAnimationInterface::LoopType::None>("eUiFlipbookAnimationLoopType_None")
  246. ->Enum<(int)UiFlipbookAnimationInterface::LoopType::Linear>("eUiFlipbookAnimationLoopType_Linear")
  247. ->Enum<(int)UiFlipbookAnimationInterface::LoopType::PingPong>("eUiFlipbookAnimationLoopType_PingPong")
  248. ;
  249. behaviorContext->Enum<(int)UiFlipbookAnimationInterface::FramerateUnits::FPS>("eUiFlipbookAnimationFramerateUnits_FPS")
  250. ->Enum<(int)UiFlipbookAnimationInterface::FramerateUnits::SecondsPerFrame>("eUiFlipbookAnimationFramerateUnits_SecondsPerFrame")
  251. ;
  252. }
  253. }
  254. ////////////////////////////////////////////////////////////////////////////////////////////////////
  255. AZ::u32 UiFlipbookAnimationComponent::GetMaxFrame() const
  256. {
  257. AZ::u32 numImageIndices = 0;
  258. UiIndexableImageBus::EventResult(numImageIndices, GetEntityId(), &UiIndexableImageBus::Events::GetImageIndexCount);
  259. return numImageIndices;
  260. }
  261. ////////////////////////////////////////////////////////////////////////////////////////////////////
  262. bool UiFlipbookAnimationComponent::FrameWithinRange(AZ::u32 frameValue)
  263. {
  264. AZ::u32 maxFrame = GetMaxFrame();
  265. return maxFrame > 0 && frameValue < maxFrame;
  266. }
  267. ////////////////////////////////////////////////////////////////////////////////////////////////////
  268. LyShine::AZu32ComboBoxVec UiFlipbookAnimationComponent::PopulateIndexStringList() const
  269. {
  270. AZ::u32 numFrames = GetMaxFrame();
  271. if (numFrames > 0)
  272. {
  273. return LyShine::GetEnumSpriteIndexList(GetEntityId(), 0, numFrames - 1);
  274. }
  275. // Add an empty element to prevent an AzToolsFramework warning that fires
  276. // when an empty container is encountered.
  277. LyShine::AZu32ComboBoxVec comboBoxVec;
  278. comboBoxVec.push_back(AZStd::make_pair(0, notConfiguredMessage));
  279. return comboBoxVec;
  280. }
  281. ////////////////////////////////////////////////////////////////////////////////////////////////////
  282. LyShine::AZu32ComboBoxVec UiFlipbookAnimationComponent::PopulateConstrainedIndexStringList() const
  283. {
  284. const char* errorMessage = notConfiguredMessage;
  285. AZ::u32 indexCount = GetMaxFrame();
  286. const bool isIndexedImage = indexCount > 1;
  287. if (isIndexedImage)
  288. {
  289. errorMessage = "<Invalid loop range>";
  290. }
  291. return LyShine::GetEnumSpriteIndexList(GetEntityId(), m_startFrame, m_endFrame, errorMessage);
  292. }
  293. ////////////////////////////////////////////////////////////////////////////////////////////////////
  294. void UiFlipbookAnimationComponent::OnStartFrameChange()
  295. {
  296. m_endFrame = AZ::GetMax<AZ::u32>(m_startFrame, m_endFrame);
  297. m_currentFrame = AZ::GetClamp<AZ::u32>(m_currentFrame, m_startFrame, m_endFrame);
  298. m_loopStartFrame = AZ::GetClamp<AZ::u32>(m_loopStartFrame, m_startFrame, m_endFrame);
  299. }
  300. ////////////////////////////////////////////////////////////////////////////////////////////////////
  301. void UiFlipbookAnimationComponent::OnEndFrameChange()
  302. {
  303. m_startFrame = AZ::GetMin<AZ::u32>(m_startFrame, m_endFrame);
  304. m_currentFrame = AZ::GetClamp<AZ::u32>(m_currentFrame, m_startFrame, m_endFrame);
  305. m_loopStartFrame = AZ::GetClamp<AZ::u32>(m_loopStartFrame, m_startFrame, m_endFrame);
  306. }
  307. void UiFlipbookAnimationComponent::OnFramerateUnitChange()
  308. {
  309. AZ_Assert(m_framerateUnit == FramerateUnits::FPS || m_framerateUnit == FramerateUnits::SecondsPerFrame,
  310. "New framerate unit added for flipbooks - please update this function accordingly!");
  311. m_framerate = m_framerate != 0.0f ? 1.0f / m_framerate : 0.0f;
  312. }
  313. ////////////////////////////////////////////////////////////////////////////////////////////////////
  314. bool UiFlipbookAnimationComponent::IsPingPongLoopType() const
  315. {
  316. return m_loopType == LoopType::PingPong;
  317. }
  318. ////////////////////////////////////////////////////////////////////////////////////////////////////
  319. bool UiFlipbookAnimationComponent::IsLoopingType() const
  320. {
  321. return m_loopType != LoopType::None;
  322. }
  323. ////////////////////////////////////////////////////////////////////////////////////////////////////
  324. float UiFlipbookAnimationComponent::CalculateLoopDelay() const
  325. {
  326. float loopDelay = 0.0f;
  327. if (IsLoopingType())
  328. {
  329. const bool isStartFrame = m_currentFrame == m_loopStartFrame;
  330. const bool playingIntro = m_prevFrame < m_currentFrame && m_startFrame != m_loopStartFrame;
  331. const bool shouldApplyStartLoopDelay = isStartFrame && !playingIntro;
  332. if (shouldApplyStartLoopDelay)
  333. {
  334. loopDelay = m_loopDelay;
  335. }
  336. else if (m_loopType == LoopType::PingPong)
  337. {
  338. const bool isEndFrame = m_currentFrame == m_endFrame;
  339. const bool isPlayingReverse = m_currentLoopDirection < 0;
  340. const bool shouldApplyReverseDelay = isEndFrame && isPlayingReverse;
  341. if (shouldApplyReverseDelay)
  342. {
  343. loopDelay = m_reverseDelay;
  344. }
  345. }
  346. }
  347. return loopDelay;
  348. }
  349. ////////////////////////////////////////////////////////////////////////////////////////////////////
  350. void UiFlipbookAnimationComponent::Activate()
  351. {
  352. UiFlipbookAnimationBus::Handler::BusConnect(GetEntityId());
  353. UiInitializationBus::Handler::BusConnect(GetEntityId());
  354. UiSpriteSourceNotificationBus::Handler::BusConnect(GetEntityId());
  355. if (m_isPlaying)
  356. {
  357. // this is unlikely but possible. To get here a client would have to start the flipbook
  358. // playing and then deactivate and reactivate (e.g. add a component).
  359. AZ::EntityId canvasEntityId;
  360. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  361. if (canvasEntityId.IsValid())
  362. {
  363. UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId);
  364. }
  365. }
  366. }
  367. ////////////////////////////////////////////////////////////////////////////////////////////////////
  368. void UiFlipbookAnimationComponent::Deactivate()
  369. {
  370. UiFlipbookAnimationBus::Handler::BusDisconnect();
  371. UiInitializationBus::Handler::BusDisconnect();
  372. UiCanvasUpdateNotificationBus::Handler::BusDisconnect();
  373. UiSpriteSourceNotificationBus::Handler::BusDisconnect();
  374. }
  375. ////////////////////////////////////////////////////////////////////////////////////////////////////
  376. void UiFlipbookAnimationComponent::Update(float deltaTime)
  377. {
  378. if (m_isPlaying)
  379. {
  380. m_elapsedTime += deltaTime;
  381. if (m_useStartDelay)
  382. {
  383. if (m_elapsedTime >= m_startDelay)
  384. {
  385. m_useStartDelay = false;
  386. m_elapsedTime = 0.0f;
  387. UiIndexableImageBus::Event(GetEntityId(), &UiIndexableImageBus::Events::SetImageIndex, m_currentFrame);
  388. }
  389. return;
  390. }
  391. const float loopDelay = CalculateLoopDelay();
  392. // Calculate the frame delay (time to transition to next frame) based on framerate.
  393. // If framerate is in FPS we convert to seconds-per-frame to test against elapsedTime.
  394. const float frameDelay = CalculateFramerateAsSecondsPerFrame();
  395. if (m_elapsedTime >= (frameDelay + loopDelay))
  396. {
  397. // Determine the number of frames that has elapsed and adjust
  398. // "elapsed time" to account for any additional time that has
  399. // passed given the current delta.
  400. const float elapsedTimeAfterDelayFrame = m_elapsedTime - (frameDelay + loopDelay);
  401. const AZ::s32 numFramesElapsed = static_cast<AZ::s32>(1 + (elapsedTimeAfterDelayFrame / frameDelay));
  402. m_elapsedTime = m_elapsedTime - ((numFramesElapsed * frameDelay) + loopDelay);
  403. // In case the loop direction is negative, we don't want to
  404. // subtract from the current frame if its zero.
  405. m_prevFrame = m_currentFrame;
  406. const AZ::s32 nextFrameNum = AZ::GetMax<AZ::s32>(0, static_cast<AZ::s32>(m_currentFrame) + numFramesElapsed * m_currentLoopDirection);
  407. m_currentFrame = static_cast<AZ::u32>(nextFrameNum);
  408. switch (m_loopType)
  409. {
  410. case LoopType::None:
  411. if (m_currentFrame > m_endFrame)
  412. {
  413. m_currentFrame = m_endFrame;
  414. Stop();
  415. }
  416. break;
  417. case LoopType::Linear:
  418. if (m_currentFrame > m_endFrame)
  419. {
  420. m_currentFrame = m_loopStartFrame;
  421. UiFlipbookAnimationNotificationsBus::Event(
  422. GetEntityId(), &UiFlipbookAnimationNotificationsBus::Events::OnLoopSequenceCompleted);
  423. }
  424. break;
  425. case LoopType::PingPong:
  426. if (m_currentLoopDirection > 0 && m_currentFrame >= m_endFrame)
  427. {
  428. m_currentLoopDirection = -1;
  429. m_currentFrame = m_endFrame;
  430. UiFlipbookAnimationNotificationsBus::Event(
  431. GetEntityId(), &UiFlipbookAnimationNotificationsBus::Events::OnLoopSequenceCompleted);
  432. }
  433. else if (m_currentLoopDirection < 0 && m_currentFrame <= m_loopStartFrame)
  434. {
  435. m_currentLoopDirection = 1;
  436. m_currentFrame = m_loopStartFrame;
  437. UiFlipbookAnimationNotificationsBus::Event(
  438. GetEntityId(), &UiFlipbookAnimationNotificationsBus::Events::OnLoopSequenceCompleted);
  439. }
  440. break;
  441. default:
  442. break;
  443. }
  444. // Show current frame
  445. UiIndexableImageBus::Event(GetEntityId(), &UiIndexableImageBus::Events::SetImageIndex, m_currentFrame);
  446. }
  447. }
  448. }
  449. ////////////////////////////////////////////////////////////////////////////////////////////////////
  450. void UiFlipbookAnimationComponent::InGamePostActivate()
  451. {
  452. if (m_isPlaying)
  453. {
  454. // Could get here if Start was called from Lua in the OnActivate function
  455. if (!UiCanvasUpdateNotificationBus::Handler::BusIsConnected())
  456. {
  457. AZ::EntityId canvasEntityId;
  458. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  459. if (canvasEntityId.IsValid())
  460. {
  461. UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId);
  462. }
  463. }
  464. }
  465. else if (m_isAutoPlay)
  466. {
  467. Start();
  468. }
  469. }
  470. ////////////////////////////////////////////////////////////////////////////////////////////////////
  471. void UiFlipbookAnimationComponent::Start()
  472. {
  473. m_currentFrame = m_startFrame;
  474. m_currentLoopDirection = 1;
  475. m_isPlaying = true;
  476. m_elapsedTime = 0.0f;
  477. m_useStartDelay = m_startDelay > 0.0f ? true : false;
  478. // Show current frame
  479. if (!m_useStartDelay)
  480. {
  481. UiIndexableImageBus::Event(GetEntityId(), &UiIndexableImageBus::Events::SetImageIndex, m_currentFrame);
  482. }
  483. // Start the update loop
  484. if (!UiCanvasUpdateNotificationBus::Handler::BusIsConnected())
  485. {
  486. AZ::EntityId canvasEntityId;
  487. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  488. // if this element has not been fixed up yet then canvasEntityId will be invalid. We handle this
  489. // in InGamePostActivate
  490. if (canvasEntityId.IsValid())
  491. {
  492. UiCanvasUpdateNotificationBus::Handler::BusConnect(canvasEntityId);
  493. }
  494. }
  495. // Let listeners know that we started playing
  496. UiFlipbookAnimationNotificationsBus::Event(GetEntityId(), &UiFlipbookAnimationNotificationsBus::Events::OnAnimationStarted);
  497. }
  498. ////////////////////////////////////////////////////////////////////////////////////////////////////
  499. void UiFlipbookAnimationComponent::Stop()
  500. {
  501. m_isPlaying = false;
  502. UiCanvasUpdateNotificationBus::Handler::BusDisconnect();
  503. UiFlipbookAnimationNotificationsBus::Event(GetEntityId(), &UiFlipbookAnimationNotificationsBus::Events::OnAnimationStopped);
  504. }
  505. ////////////////////////////////////////////////////////////////////////////////////////////////////
  506. void UiFlipbookAnimationComponent::SetStartFrame(AZ::u32 startFrame)
  507. {
  508. if (!FrameWithinRange(startFrame))
  509. {
  510. AZ_Warning("UI", false, "Invalid frame value given: %u", startFrame);
  511. return;
  512. }
  513. m_startFrame = startFrame;
  514. OnStartFrameChange();
  515. }
  516. ////////////////////////////////////////////////////////////////////////////////////////////////////
  517. void UiFlipbookAnimationComponent::SetEndFrame(AZ::u32 endFrame)
  518. {
  519. if (!FrameWithinRange(endFrame))
  520. {
  521. AZ_Warning("UI", false, "Invalid frame value given: %u", endFrame);
  522. return;
  523. }
  524. m_endFrame = endFrame;
  525. OnEndFrameChange();
  526. }
  527. ////////////////////////////////////////////////////////////////////////////////////////////////////
  528. void UiFlipbookAnimationComponent::SetCurrentFrame(AZ::u32 currentFrame)
  529. {
  530. // The current frame needs to stay between the start and end frames
  531. const bool validFrameValue = currentFrame >= m_startFrame && currentFrame <= m_endFrame;
  532. if (!validFrameValue)
  533. {
  534. AZ_Warning("UI", false, "Invalid frame value given: %u", currentFrame);
  535. return;
  536. }
  537. m_currentFrame = currentFrame;
  538. UiIndexableImageBus::Event(GetEntityId(), &UiIndexableImageBus::Events::SetImageIndex, m_currentFrame);
  539. }
  540. ////////////////////////////////////////////////////////////////////////////////////////////////////
  541. void UiFlipbookAnimationComponent::SetLoopStartFrame(AZ::u32 loopStartFrame)
  542. {
  543. // Ensure that loop start frame exists within start and end frame range
  544. const bool validFrameValue = loopStartFrame >= m_startFrame && loopStartFrame <= m_endFrame;
  545. if (!validFrameValue)
  546. {
  547. AZ_Warning("UI", false, "Invalid frame value given: %u", loopStartFrame);
  548. return;
  549. }
  550. m_loopStartFrame = loopStartFrame;
  551. }
  552. ////////////////////////////////////////////////////////////////////////////////////////////////////
  553. void UiFlipbookAnimationComponent::SetLoopType(UiFlipbookAnimationInterface::LoopType loopType)
  554. {
  555. m_loopType = loopType;
  556. // PingPong is currently the only loop type that supports a negative loop
  557. // direction.
  558. if (m_loopType != LoopType::PingPong)
  559. {
  560. m_currentLoopDirection = 1;
  561. }
  562. }
  563. ////////////////////////////////////////////////////////////////////////////////////////////////////
  564. void UiFlipbookAnimationComponent::OnSpriteSourceChanged()
  565. {
  566. AZ::u32 indexCount = GetMaxFrame();
  567. const AZ::u32 newStartFrame = AZ::GetClamp<AZ::u32>(m_startFrame, 0, indexCount - 1);
  568. const AZ::u32 newEndFrame = AZ::GetClamp<AZ::u32>(m_endFrame, 0, indexCount - 1);
  569. const bool frameRangesChanged = newStartFrame != m_startFrame || newEndFrame != m_endFrame;
  570. if (frameRangesChanged)
  571. {
  572. m_startFrame = newStartFrame;
  573. m_endFrame = newEndFrame;
  574. OnStartFrameChange();
  575. OnEndFrameChange();
  576. }
  577. }