diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp index a5abf2221..53453afe9 100644 --- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp @@ -336,7 +336,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI continue; } - SceneObject* obj = SceneObjectsFactory::Spawn(context, *(ISerializable::DeserializeStream*)data); + SceneObject* obj = SceneObjectsFactory::Spawn(context, *data); if (!obj) continue; obj->RegisterObject(); diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 9c1060c0f..c2c2463b2 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -2,6 +2,7 @@ #include "Prefab.h" #include "Engine/Serialization/JsonTools.h" +#include "Engine/Content/Content.h" #include "Engine/Content/Factories/JsonAssetFactory.h" #include "Engine/Core/Log.h" #include "Engine/Level/Prefabs/PrefabManager.h" @@ -21,6 +22,36 @@ Prefab::Prefab(const SpawnParams& params, const AssetInfo* info) { } +Guid Prefab::GetRootObjectId() const +{ + ASSERT(IsLoaded()); + ScopeLock lock(Locker); + + // Root is always the first but handle case when prefab root was reordered in the base prefab while the nested prefab has still the old state + // TODO: resave and force sync prefabs during game cooking so this step could be skipped in game + int32 objectIndex = 0; + if (NestedPrefabs.HasItems()) + { + const auto& data = *Data; + const Guid basePrefabId = JsonTools::GetGuid(data[objectIndex], "PrefabID"); + if (const auto basePrefab = Content::Load(basePrefabId)) + { + const Guid basePrefabRootId = basePrefab->GetRootObjectId(); + for (int32 i = 0; i < ObjectsCount; i++) + { + const Guid prefabObjectId = JsonTools::GetGuid(data[i], "PrefabObjectID"); + if (prefabObjectId == basePrefabRootId) + { + objectIndex = i; + break; + } + } + } + } + + return ObjectsIds[objectIndex]; +} + Actor* Prefab::GetDefaultInstance() { ScopeLock lock(Locker); diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h index ed82473c9..5e0075edf 100644 --- a/Source/Engine/Level/Prefabs/Prefab.h +++ b/Source/Engine/Level/Prefabs/Prefab.h @@ -50,11 +50,7 @@ public: /// /// Gets the root object identifier (prefab object ID). Asset must be loaded. /// - Guid GetRootObjectId() const - { - ASSERT(IsLoaded()); - return ObjectsIds[0]; - } + Guid GetRootObjectId() const; /// /// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done. diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index cc42967a4..88d80a41a 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -121,7 +121,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryAt(i) = obj; if (obj) obj->RegisterObject(); @@ -146,16 +146,25 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryIsEmpty()) + // Pick prefab root object + if (sceneObjects->IsEmpty()) { LOG(Warning, "No valid objects in prefab."); return nullptr; } - auto root = (Actor*)sceneObjects.Value->At(0); + Actor* root = nullptr; + const Guid prefabRootObjectId = prefab->GetRootObjectId(); + for (int32 i = 0; i < objectsCount; i++) + { + if (JsonTools::GetGuid(data[i], "ID") == prefabRootObjectId) + { + root = dynamic_cast(sceneObjects->At(i)); + break; + } + } if (!root) { - LOG(Warning, "Failed to load prefab root object."); + LOG(Warning, "Missing prefab root object."); return nullptr; } @@ -167,6 +176,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary_parent) + root->_parent->Children.Remove(root); root->_parent = parent; if (parent) parent->Children.Add(root); @@ -174,16 +185,16 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryCount(); i++) { - auto obj = sceneObjects->At(i); + SceneObject* obj = sceneObjects->At(i); if (obj) obj->Initialize(); } // Delete objects without parent or with invalid linkage to the prefab - for (int32 i = 1; i < sceneObjects->Count(); i++) + for (int32 i = 0; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); - if (!obj) + if (!obj || obj == root) continue; // Check for missing parent (eg. parent object has been deleted) @@ -251,8 +262,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryAdd(prefabObjectId, obj); - obj->_prefabID = prefabId; - obj->_prefabObjectID = prefabObjectId; + obj->LinkPrefab(prefabId, prefabObjectId); } // Update transformations @@ -317,7 +327,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat writer.StartArray(); for (int32 i = 0; i < sceneObjects->Count(); i++) { - SceneObject* obj = sceneObjects.Value->At(i); + SceneObject* obj = sceneObjects->At(i); writer.SceneObject(obj); } writer.EndArray(); @@ -335,7 +345,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat for (int32 i = 0; i < sceneObjects->Count(); i++) { // Generate new IDs for the prefab objects (other than reference instance used to create prefab) - const SceneObject* obj = sceneObjects.Value->At(i); + const SceneObject* obj = sceneObjects->At(i); objectInstanceIdToPrefabObjectId.Add(obj->GetSceneObjectId(), Guid::New()); } { @@ -382,7 +392,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat for (int32 i = 0; i < sceneObjects->Count(); i++) { - SceneObject* obj = sceneObjects.Value->At(i); + SceneObject* obj = sceneObjects->At(i); Guid prefabObjectId; if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId)) diff --git a/Source/Engine/Level/SceneObject.h b/Source/Engine/Level/SceneObject.h index 0425a3a76..64bda4a59 100644 --- a/Source/Engine/Level/SceneObject.h +++ b/Source/Engine/Level/SceneObject.h @@ -58,6 +58,7 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API SceneObject : public Scripting { DECLARE_SCRIPTING_TYPE_NO_SPAWN(SceneObject); friend PrefabInstanceData; + friend PrefabManager; friend Actor; friend Level; friend ScriptsFactory; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 35629dc6a..00fd29be0 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -31,7 +31,7 @@ void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj) } } -SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::DeserializeStream& stream) +SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::DeserializeStream& stream) { // Get object id Guid id = JsonTools::GetGuid(stream, "ID"); @@ -81,7 +81,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::Deseria context.Modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) - obj = Spawn(context, *(ISerializable::DeserializeStream*)prefabData); + obj = Spawn(context, *prefabData); } else { @@ -584,7 +584,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS data.Modifier->IdsMapping[prefabObjectId] = id; // Create prefab instance (recursive prefab loading to support nested prefabs) - auto child = Spawn(context, *(ISerializable::DeserializeStream*)prefabData); + auto child = Spawn(context, *prefabData); if (!child) { LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId); diff --git a/Source/Engine/Level/SceneObjectsFactory.h b/Source/Engine/Level/SceneObjectsFactory.h index 1534b7359..d3f5b3dd7 100644 --- a/Source/Engine/Level/SceneObjectsFactory.h +++ b/Source/Engine/Level/SceneObjectsFactory.h @@ -35,7 +35,7 @@ public: /// /// The serialization context. /// The serialized data stream. - static SceneObject* Spawn(Context& context, ISerializable::DeserializeStream& stream); + static SceneObject* Spawn(Context& context, const ISerializable::DeserializeStream& stream); /// /// Deserializes the scene object from the specified data value.