From 22e893ccbe4467cc4db328fd9c3c408c94e1a569 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 20 May 2021 16:32:41 +0100 Subject: [PATCH] Added support for nested prefabs in multiplayer pipeline --- .../Pipeline/NetworkPrefabProcessor.cpp | 111 ++++++++++-------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 0bbfe17801..762219cce4 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -54,34 +54,15 @@ namespace Multiplayer } } - static AZStd::vector GetEntitiesFromInstance(AZStd::unique_ptr& instance) - { - AZStd::vector result; - - instance->GetNestedEntities([&result](const AZStd::unique_ptr& entity) { - result.emplace_back(entity.get()); - return true; - }); - - if (instance->HasContainerEntity()) - { - auto containerEntityReference = instance->GetContainerEntity(); - result.emplace_back(&containerEntityReference->get()); - } - - return result; - } - - void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) + static AZStd::unique_ptr LoadInstanceFromPrefab(const PrefabDom& prefab) { using namespace AzToolsFramework::Prefab; // convert Prefab DOM into Prefab Instance. AZStd::unique_ptr sourceInstance(aznew Instance()); - if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, - PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) + if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*sourceInstance, prefab, PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) { - PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); + PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName); AZStd::string errorMessage("NetworkPrefabProcessor: Failed to Load Prefab Instance from given Prefab Dom."); if (sourceReference.has_value() && sourceReference->get().IsString() && sourceReference->get().GetStringLength() != 0) @@ -90,6 +71,38 @@ namespace Multiplayer errorMessage += AZStd::string::format("Prefab Source: %.*s", AZ_STRING_ARG(source)); } AZ_Error("NetworkPrefabProcessor", false, errorMessage.c_str()); + return nullptr; + } + return sourceInstance; + } + + static void GatherNetEntities( + AzToolsFramework::Prefab::Instance* instance, + AZStd::vector>& output) + { + instance->GetEntities([instance, &output](AZStd::unique_ptr& prefabEntity) + { + if (prefabEntity->FindComponent()) + { + output.push_back(AZStd::make_pair(prefabEntity.get(), instance)); + } + return true; + }); + + instance->GetNestedInstances([&output](AZStd::unique_ptr& nestedInstance) + { + GatherNetEntities(nestedInstance.get(), output); + }); + } + + void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) + { + using namespace AzToolsFramework::Prefab; + + // convert Prefab DOM into Prefab Instance. + AZStd::unique_ptr sourceInstance = LoadInstanceFromPrefab(prefab); + if (!sourceInstance) + { return; } @@ -105,36 +118,37 @@ namespace Multiplayer auto&& [object, networkSpawnable] = ProcessedObjectStore::Create(uniqueName, context.GetSourceUuid(), AZStd::move(serializer)); - // grab all nested entities from the Instance as source entities. - AZStd::vector sourceEntities = GetEntitiesFromInstance(sourceInstance); - AZStd::vector networkedEntityIds; - networkedEntityIds.reserve(sourceEntities.size()); + // Grab all net entities with their corresponding Instances to handle nested prefabs correctly + AZStd::vector> netEntities; + GatherNetEntities(sourceInstance.get(), netEntities); - for (auto* sourceEntity : sourceEntities) - { - if (sourceEntity->FindComponent()) - { - networkedEntityIds.push_back(sourceEntity->GetId()); - } - } + // Instance container for net entities + AZStd::unique_ptr networkInstance(aznew Instance()); - if (networkedEntityIds.empty()) + // Create an asset for our future network spawnable: this allows us to put references to the asset in the components + AZ::Data::Asset networkSpawnableAsset; + networkSpawnableAsset.Create(networkSpawnable->GetId()); + networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); + + if (netEntities.empty()) { // No networked entities in the prefab, no need to do anything in this processor. return; } - AZStd::unique_ptr networkInstance(aznew Instance()); + // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 + size_t netEntitiesIndexCounter = 1; - AZ::Data::Asset networkSpawnableAsset; - networkSpawnableAsset.Create(networkSpawnable->GetId()); - networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - - for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex) + for (auto& entityInstancePair : netEntities) { - AZ::EntityId entityId = networkedEntityIds[entityIndex]; + AZ::Entity* prefabEntity = entityInstancePair.first; + Instance* instance = entityInstancePair.second; + + AZ::EntityId entityId = prefabEntity->GetId(); + AZ::Entity* netEntity = instance->DetachEntity(entityId).release(); + AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance", + prefabEntity->GetName().c_str(), entityId.ToString().c_str()); - AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release(); // Net entity will need a new ID to avoid IDs collision netEntity->SetId(AZ::Entity::MakeId()); networkInstance->AddEntity(*netEntity); @@ -143,17 +157,21 @@ namespace Multiplayer AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); + // Marker component is what is responsible to spawning entities based on the index. NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); - // Each spawnable has a root meta-data entity at position 0, so starting net indices from 1 - netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1); + netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter); netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); + + // Copy the transform component from the original entity to have the correct transform and parent-child relationship AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); breadcrumbEntity->CreateComponent(*transformComponent); - sourceInstance->AddEntity(*breadcrumbEntity); + instance->AddEntity(*breadcrumbEntity); + + netEntitiesIndexCounter++; } - // Add net spawnable asset holder + // Add net spawnable asset holder to the prefab root { EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity(); if (containerEntityRef.has_value()) @@ -184,7 +202,6 @@ namespace Multiplayer return; } - bool result = SpawnableUtils::CreateSpawnable(*networkSpawnable, networkPrefab); if (result) {