Fix spawning nested prefab with different root actor

#1138
This commit is contained in:
Wojtek Figat
2023-06-11 20:43:31 +02:00
parent 60ddf0ea89
commit 958c7b2181
7 changed files with 61 additions and 23 deletions

View File

@@ -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();

View File

@@ -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<Prefab>(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);

View File

@@ -50,11 +50,7 @@ public:
/// <summary>
/// Gets the root object identifier (prefab object ID). Asset must be loaded.
/// </summary>
Guid GetRootObjectId() const
{
ASSERT(IsLoaded());
return ObjectsIds[0];
}
Guid GetRootObjectId() const;
/// <summary>
/// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done.

View File

@@ -121,7 +121,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
for (int32 i = 0; i < objectsCount; i++)
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
SceneObject* obj = SceneObjectsFactory::Spawn(context, stream);
sceneObjects->At(i) = obj;
if (obj)
obj->RegisterObject();
@@ -146,16 +146,25 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
}
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Assume that prefab has always only one root actor that is serialized first
if (sceneObjects.Value->IsEmpty())
// 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<Actor*>(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<Guid
}
// Prepare parent linkage for prefab root actor
if (root->_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, Dictionary<Guid
// Link actors hierarchy
for (int32 i = 0; i < sceneObjects->Count(); 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, Dictionary<Guid
const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID");
if (objectsCache)
objectsCache->Add(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))

View File

@@ -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;

View File

@@ -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);

View File

@@ -35,7 +35,7 @@ public:
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="stream">The serialized data stream.</param>
static SceneObject* Spawn(Context& context, ISerializable::DeserializeStream& stream);
static SceneObject* Spawn(Context& context, const ISerializable::DeserializeStream& stream);
/// <summary>
/// Deserializes the scene object from the specified data value.