3
0

UiSpawnerComponent.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 "UiSpawnerComponent.h"
  9. #include <AzCore/Asset/AssetSerializer.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Component/ComponentApplicationBus.h>
  13. #include <AzCore/Component/Entity.h>
  14. #include <AzCore/RTTI/BehaviorContext.h>
  15. #include <AzCore/Asset/AssetManagerBus.h>
  16. #include <LyShine/Bus/UiGameEntityContextBus.h>
  17. #include <LyShine/Bus/UiElementBus.h>
  18. // BehaviorContext UiSpawnerNotificationBus forwarder
  19. class BehaviorUiSpawnerNotificationBusHandler
  20. : public UiSpawnerNotificationBus::Handler
  21. , public AZ::BehaviorEBusHandler
  22. {
  23. public:
  24. AZ_EBUS_BEHAVIOR_BINDER(BehaviorUiSpawnerNotificationBusHandler, "{95213AF9-F8F4-4D86-8C68-625F5AFE78FA}", AZ::SystemAllocator,
  25. OnSpawnBegin, OnEntitySpawned, OnEntitiesSpawned, OnTopLevelEntitiesSpawned, OnSpawnEnd, OnSpawnFailed);
  26. void OnSpawnBegin(const AzFramework::SliceInstantiationTicket& ticket) override
  27. {
  28. Call(FN_OnSpawnBegin, ticket);
  29. }
  30. void OnEntitySpawned(const AzFramework::SliceInstantiationTicket& ticket, const AZ::EntityId& id) override
  31. {
  32. Call(FN_OnEntitySpawned, ticket, id);
  33. }
  34. void OnEntitiesSpawned(const AzFramework::SliceInstantiationTicket& ticket, const AZStd::vector<AZ::EntityId>& spawnedEntities) override
  35. {
  36. Call(FN_OnEntitiesSpawned, ticket, spawnedEntities);
  37. }
  38. void OnTopLevelEntitiesSpawned(const AzFramework::SliceInstantiationTicket& ticket, const AZStd::vector<AZ::EntityId>& spawnedEntities) override
  39. {
  40. Call(FN_OnTopLevelEntitiesSpawned, ticket, spawnedEntities);
  41. }
  42. void OnSpawnEnd(const AzFramework::SliceInstantiationTicket& ticket) override
  43. {
  44. Call(FN_OnSpawnEnd, ticket);
  45. }
  46. void OnSpawnFailed(const AzFramework::SliceInstantiationTicket& ticket) override
  47. {
  48. Call(FN_OnSpawnFailed, ticket);
  49. }
  50. };
  51. ////////////////////////////////////////////////////////////////////////////////////////////////////
  52. void UiSpawnerComponent::Reflect(AZ::ReflectContext* context)
  53. {
  54. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  55. if (serializeContext)
  56. {
  57. serializeContext->Class<UiSpawnerComponent, AZ::Component>()
  58. ->Version(1)
  59. ->Field("Slice", &UiSpawnerComponent::m_sliceAsset)
  60. ->Field("SpawnOnActivate", &UiSpawnerComponent::m_spawnOnActivate);
  61. AZ::EditContext* editContext = serializeContext->GetEditContext();
  62. if (editContext)
  63. {
  64. auto editInfo = editContext->Class<UiSpawnerComponent>("UiSpawner",
  65. "The spawner component provides dynamic UI slice spawning support.");
  66. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  67. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  68. ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Spawner.svg")
  69. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Spawner.svg")
  70. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
  71. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  72. editInfo->DataElement(0, &UiSpawnerComponent::m_sliceAsset, "Dynamic slice", "The slice to spawn");
  73. editInfo->DataElement(0, &UiSpawnerComponent::m_spawnOnActivate, "Spawn on activate",
  74. "Should the component spawn the selected slice upon activation?");
  75. }
  76. }
  77. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  78. if (behaviorContext)
  79. {
  80. behaviorContext->EBus<UiSpawnerBus>("UiSpawnerBus")
  81. ->Event("Spawn", &UiSpawnerBus::Events::Spawn)
  82. ->Event("SpawnRelative", &UiSpawnerBus::Events::SpawnRelative)
  83. ->Event("SpawnAbsolute", &UiSpawnerBus::Events::SpawnViewport)
  84. ;
  85. behaviorContext->EBus<UiSpawnerNotificationBus>("UiSpawnerNotificationBus")
  86. ->Handler<BehaviorUiSpawnerNotificationBusHandler>()
  87. ;
  88. }
  89. }
  90. ////////////////////////////////////////////////////////////////////////////////////////////////////
  91. void UiSpawnerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  92. {
  93. provided.push_back(AZ_CRC("SpawnerService", 0xd2f1d7a3));
  94. }
  95. ////////////////////////////////////////////////////////////////////////////////////////////////////
  96. void UiSpawnerComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
  97. {
  98. dependent.push_back(AZ_CRC("TransformService", 0x8ee22c50));
  99. }
  100. ////////////////////////////////////////////////////////////////////////////////////////////////////
  101. UiSpawnerComponent::UiSpawnerComponent()
  102. {
  103. // Slice asset should load purely on-demand.
  104. m_sliceAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::NoLoad);
  105. }
  106. ////////////////////////////////////////////////////////////////////////////////////////////////////
  107. void UiSpawnerComponent::Activate()
  108. {
  109. UiSpawnerBus::Handler::BusConnect(GetEntityId());
  110. if (m_spawnOnActivate)
  111. {
  112. SpawnSliceInternal(m_sliceAsset, AZ::Vector2(0.0f, 0.0f), false);
  113. }
  114. }
  115. ////////////////////////////////////////////////////////////////////////////////////////////////////
  116. void UiSpawnerComponent::Deactivate()
  117. {
  118. UiSpawnerBus::Handler::BusDisconnect();
  119. UiGameEntityContextSliceInstantiationResultsBus::MultiHandler::BusDisconnect();
  120. }
  121. ////////////////////////////////////////////////////////////////////////////////////////////////////
  122. AzFramework::SliceInstantiationTicket UiSpawnerComponent::Spawn()
  123. {
  124. return SpawnSliceInternal(m_sliceAsset, AZ::Vector2(0.0f, 0.0f), false);
  125. }
  126. ////////////////////////////////////////////////////////////////////////////////////////////////////
  127. AzFramework::SliceInstantiationTicket UiSpawnerComponent::SpawnRelative(const AZ::Vector2& relative)
  128. {
  129. return SpawnSliceInternal(m_sliceAsset, relative, false);
  130. }
  131. ////////////////////////////////////////////////////////////////////////////////////////////////////
  132. AzFramework::SliceInstantiationTicket UiSpawnerComponent::SpawnViewport(const AZ::Vector2& pos)
  133. {
  134. return SpawnSliceInternal(m_sliceAsset, pos, true);
  135. }
  136. ////////////////////////////////////////////////////////////////////////////////////////////////////
  137. AzFramework::SliceInstantiationTicket UiSpawnerComponent::SpawnSlice(const AZ::Data::Asset<AZ::Data::AssetData>& slice)
  138. {
  139. return SpawnSliceInternal(slice, AZ::Vector2(0.0f, 0.0f), false);
  140. }
  141. ////////////////////////////////////////////////////////////////////////////////////////////////////
  142. AzFramework::SliceInstantiationTicket UiSpawnerComponent::SpawnSliceRelative(const AZ::Data::Asset<AZ::Data::AssetData>& slice, const AZ::Vector2& relative)
  143. {
  144. return SpawnSliceInternal(slice, relative, false);
  145. }
  146. ////////////////////////////////////////////////////////////////////////////////////////////////////
  147. AzFramework::SliceInstantiationTicket UiSpawnerComponent::SpawnSliceViewport(const AZ::Data::Asset<AZ::Data::AssetData>& slice, const AZ::Vector2& pos)
  148. {
  149. return SpawnSliceInternal(slice, pos, true);
  150. }
  151. ////////////////////////////////////////////////////////////////////////////////////////////////////
  152. void UiSpawnerComponent::OnEntityContextSlicePreInstantiate(const AZ::Data::AssetId& /*sliceAssetId*/, const AZ::SliceComponent::SliceInstanceAddress& /*sliceAddress*/)
  153. {
  154. const AzFramework::SliceInstantiationTicket ticket = (*UiGameEntityContextSliceInstantiationResultsBus::GetCurrentBusId());
  155. UiSpawnerNotificationBus::Event(GetEntityId(), &UiSpawnerNotificationBus::Events::OnSpawnBegin, ticket);
  156. }
  157. ////////////////////////////////////////////////////////////////////////////////////////////////////
  158. void UiSpawnerComponent::OnEntityContextSliceInstantiated([[maybe_unused]] const AZ::Data::AssetId& sliceAssetId, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
  159. {
  160. const AzFramework::SliceInstantiationTicket ticket = (*UiGameEntityContextSliceInstantiationResultsBus::GetCurrentBusId());
  161. // Stop listening for this ticket (since it's done). We can have have multiple tickets in flight.
  162. UiGameEntityContextSliceInstantiationResultsBus::MultiHandler::BusDisconnect(ticket);
  163. const AZ::SliceComponent::EntityList& entities = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
  164. // first, send a notification of every individual entity that has been spawned (including top-level elements)
  165. AZStd::vector<AZ::EntityId> entityIds;
  166. entityIds.reserve(entities.size());
  167. for (AZ::Entity* currEntity : entities)
  168. {
  169. entityIds.push_back(currEntity->GetId());
  170. UiSpawnerNotificationBus::Event(GetEntityId(), &UiSpawnerNotificationBus::Events::OnEntitySpawned, ticket, currEntity->GetId());
  171. }
  172. // Then send one notification with all the entities spawned for this ticket
  173. UiSpawnerNotificationBus::Event(GetEntityId(), &UiSpawnerNotificationBus::Events::OnEntitiesSpawned, ticket, entityIds);
  174. // Then send notifications for all top level entities (there is usually only one). This will have been
  175. // included in the OnEntitySpawned and OnEntitiesSpawned but this is probably the most useful notification
  176. AZStd::vector<AZ::EntityId> topLevelEntityIds = GetTopLevelEntities(entities);
  177. UiSpawnerNotificationBus::Event(GetEntityId(), &UiSpawnerNotificationBus::Events::OnTopLevelEntitiesSpawned, ticket, topLevelEntityIds);
  178. // last, send an "OnSpawnEnd" to indicate the end of all notifications for this ticket
  179. UiSpawnerNotificationBus::Event(GetEntityId(), &UiSpawnerNotificationBus::Events::OnSpawnEnd, ticket);
  180. }
  181. ////////////////////////////////////////////////////////////////////////////////////////////////////
  182. void UiSpawnerComponent::OnEntityContextSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId)
  183. {
  184. UiGameEntityContextSliceInstantiationResultsBus::MultiHandler::BusDisconnect(*UiGameEntityContextSliceInstantiationResultsBus::GetCurrentBusId());
  185. AZStd::string assetPath;
  186. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetPathById, sliceAssetId);
  187. if (assetPath.empty())
  188. {
  189. assetPath = sliceAssetId.ToString<AZStd::string>();
  190. }
  191. AZ_Warning("UiSpawnerComponent", false, "Slice '%s' failed to instantiate. Check that it contains UI elements.", assetPath.c_str());
  192. }
  193. ////////////////////////////////////////////////////////////////////////////////////////////////////
  194. AzFramework::SliceInstantiationTicket UiSpawnerComponent::SpawnSliceInternal(const AZ::Data::Asset<AZ::Data::AssetData>& slice, const AZ::Vector2& position, bool isViewportPosition)
  195. {
  196. AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
  197. AzFramework::EntityIdContextQueryBus::EventResult(
  198. contextId, GetEntityId(), &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
  199. AzFramework::SliceInstantiationTicket ticket;
  200. UiGameEntityContextBus::EventResult(
  201. ticket,
  202. contextId,
  203. &UiGameEntityContextBus::Events::InstantiateDynamicSlice,
  204. slice,
  205. position,
  206. isViewportPosition,
  207. GetEntity(),
  208. nullptr);
  209. UiGameEntityContextSliceInstantiationResultsBus::MultiHandler::BusConnect(ticket);
  210. return ticket;
  211. }
  212. ////////////////////////////////////////////////////////////////////////////////////////////////////
  213. AZStd::vector<AZ::EntityId> UiSpawnerComponent::GetTopLevelEntities(const AZ::SliceComponent::EntityList& entities)
  214. {
  215. // Create a set of all the top-level entities.
  216. AZStd::unordered_set<AZ::Entity*> topLevelEntities;
  217. for (AZ::Entity* entity : entities)
  218. {
  219. topLevelEntities.insert(entity);
  220. }
  221. // remove anything from the topLevelEntities set that is referenced as the child of another element in the list
  222. for (AZ::Entity* entity : entities)
  223. {
  224. LyShine::EntityArray children;
  225. UiElementBus::EventResult(children, entity->GetId(), &UiElementBus::Events::GetChildElements);
  226. for (auto child : children)
  227. {
  228. topLevelEntities.erase(child);
  229. }
  230. }
  231. // Now topLevelElements contains all of the top-level elements in the set of newly instantiated entities
  232. // Copy the topLevelEntities set into a list
  233. AZStd::vector<AZ::EntityId> result;
  234. for (auto entity : topLevelEntities)
  235. {
  236. result.push_back(entity->GetId());
  237. }
  238. return result;
  239. }