Skip to content

Adds a basic support for local actor references in Prefabs. #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion Source/PrefabricatorRuntime/Private/Asset/PrefabricatorAsset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "GameFramework/Actor.h"
#include "Internationalization/Regex.h"
#include "Misc/PackageName.h"
#include "PrefabricatorAssetUserData.h"

DEFINE_LOG_CATEGORY_STATIC(LogPrefabricatorAsset, Log, All);

Expand Down Expand Up @@ -166,6 +167,17 @@ void UPrefabricatorProperty::SaveReferencedAssetValues()
Mapping.AssetReference = SoftPath;
//if (Mapping.AssetReference.IsValid())
{
//JB: If possible we store the AssetUserData.ItemID
AActor* Actor = Cast<AActor>(SoftPath.ResolveObject());
if (Actor && Actor->GetRootComponent())
{
//JB: This could be also passed to the function, but I tried to change the API as little as possible
UPrefabricatorAssetUserData* AssetUserData = Actor->GetRootComponent()->GetAssetUserData<UPrefabricatorAssetUserData>();
if (AssetUserData)
{
Mapping.AssetItemID = AssetUserData->ItemID;
}
}
//FString ObjectPathString;
//FPackageName::ParseExportTextPath(AssetPath, &Mapping.AssetClassName, &ObjectPathString);
Mapping.AssetClassName = ClassName;
Expand All @@ -177,7 +189,7 @@ void UPrefabricatorProperty::SaveReferencedAssetValues()
}
}

void UPrefabricatorProperty::LoadReferencedAssetValues()
void UPrefabricatorProperty::LoadReferencedAssetValues(const TMap<FGuid, AActor*>& InChildActors)
{
SCOPE_CYCLE_COUNTER(STAT_LoadReferencedAssetValues);
bool bModified = false;
Expand All @@ -191,6 +203,14 @@ void UPrefabricatorProperty::LoadReferencedAssetValues()
{
//SCOPE_CYCLE_COUNTER(STAT_LoadReferencedAssetValues_GetAssetPathName);
ReferencedPath = Mapping.AssetReference.GetAssetPathName();

// JB: We try to find the corresponding actor inside the prefab using the stored ItemID.
if (InChildActors.Contains(Mapping.AssetItemID))
{
AActor* ReferencedActor = *InChildActors.Find(Mapping.AssetItemID);
ReferencedPath = *ReferencedActor->GetPathName();
}

if (ReferencedPath.ToString().IsEmpty()) {
continue;
}
Expand Down
114 changes: 76 additions & 38 deletions Source/PrefabricatorRuntime/Private/Prefab/PrefabTools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ void FPrefabTools::SaveStateToPrefabAsset(APrefabActor* PrefabActor)
}
}

// JB: Map storing all child actors based on their index in the PrefabAsset->ActorData array.
TMap<int32, AActor*> ChildrenToStore;
// JB: Prepares the actor data for all child actors and makes sure that all have ItemID.
for (AActor* ChildActor : Children) {
if (ChildActor && ChildActor->GetRootComponent()) {
UPrefabricatorAssetUserData* ChildUserData = ChildActor->GetRootComponent()->GetAssetUserData<UPrefabricatorAssetUserData>();
Expand All @@ -244,9 +247,17 @@ void FPrefabTools::SaveStateToPrefabAsset(APrefabActor* PrefabActor)
int32 NewItemIndex = PrefabAsset->ActorData.AddDefaulted();
FPrefabricatorActorData& ActorData = PrefabAsset->ActorData[NewItemIndex];
ActorData.PrefabItemID = ItemID;
SaveActorState(ChildActor, PrefabActor, ActorData);
ChildrenToStore.Add(NewItemIndex, ChildActor);
}
}

// JB: Stores the children actors state.
// JB: We need to do it after all the actor data are prepared in order to correctly reference the actors.
for (auto& ChildToStoreElement : ChildrenToStore)
{
SaveActorState(ChildToStoreElement.Value, PrefabActor, PrefabAsset->ActorData[ChildToStoreElement.Key]);
}

PrefabAsset->Version = (uint32)EPrefabricatorAssetVersion::LatestVersion;

PrefabActor->PrefabComponent->UpdateBounds();
Expand Down Expand Up @@ -301,7 +312,8 @@ namespace {
return false;
}

void DeserializeFields(UObject* InObjToDeserialize, const TArray<UPrefabricatorProperty*>& InProperties) {
// JB: The InChildActors map contains all actors spawned by the prefab stored by their ItemID.
void DeserializeFields(UObject* InObjToDeserialize, const TArray<UPrefabricatorProperty*>& InProperties, const TMap<FGuid, AActor*>& InChildActors) {
if (!InObjToDeserialize) return;

TMap<FString, UPrefabricatorProperty*> PropertiesByName;
Expand All @@ -326,7 +338,7 @@ namespace {
if (PrefabProperty) {
{
SCOPE_CYCLE_COUNTER(STAT_DeserializeFields_Iterate_LoadValue);
PrefabProperty->LoadReferencedAssetValues();
PrefabProperty->LoadReferencedAssetValues(InChildActors);
}
{
SCOPE_CYCLE_COUNTER(STAT_DeserializeFields_Iterate_SetValue);
Expand Down Expand Up @@ -372,6 +384,7 @@ namespace {

for (const UProperty* Property : PropertiesToSerialize) {
if (!Property) continue;
// JB: This was already checked few lines above and cannot ever happen - you may consider removing it.
if (FPrefabTools::ShouldIgnorePropertySerialization(Property->GetFName())) {
continue;
}
Expand Down Expand Up @@ -476,6 +489,7 @@ void FPrefabTools::SaveActorState(AActor* InActor, APrefabActor* PrefabActor, FP
int32 ComponentDataIdx = OutActorData.Components.AddDefaulted();
FPrefabricatorComponentData& ComponentData = OutActorData.Components[ComponentDataIdx];
ComponentData.ComponentName = Component->GetPathName(InActor);
// JB: As far as I can tell ComponentData.RelativeTransform is never used - you may consider removing it to save some disk space.
if (USceneComponent* SceneComponent = Cast<USceneComponent>(Component)) {
ComponentData.RelativeTransform = SceneComponent->GetComponentTransform();
}
Expand All @@ -488,7 +502,7 @@ void FPrefabTools::SaveActorState(AActor* InActor, APrefabActor* PrefabActor, FP
DumpSerializedData(OutActorData);
}

void FPrefabTools::LoadActorState(AActor* InActor, const FPrefabricatorActorData& InActorData, const FPrefabLoadSettings& InSettings)
void FPrefabTools::LoadActorState(AActor* InActor, const FPrefabricatorActorData& InActorData, const FPrefabLoadSettings& InSettings, const TMap<FGuid, AActor*>& InChildActors)
{
if (!InActor) {
return;
Expand All @@ -502,7 +516,7 @@ void FPrefabTools::LoadActorState(AActor* InActor, const FPrefabricatorActorData

{
//SCOPE_CYCLE_COUNTER(STAT_LoadStateFromPrefabAsset_DeserializeFieldsActor);
DeserializeFields(InActor, InActorData.Properties);
DeserializeFields(InActor, InActorData.Properties, InChildActors);
}

TMap<FString, UActorComponent*> ComponentsByName;
Expand All @@ -526,7 +540,7 @@ void FPrefabTools::LoadActorState(AActor* InActor, const FPrefabricatorActorData

{
//SCOPE_CYCLE_COUNTER(STAT_LoadStateFromPrefabAsset_DeserializeFieldsComponents);
DeserializeFields(Component, ComponentData.Properties);
DeserializeFields(Component, ComponentData.Properties, InChildActors);
}

{
Expand Down Expand Up @@ -637,6 +651,14 @@ void FPrefabTools::LoadStateFromPrefabAsset(APrefabActor* PrefabActor, const FPr
}
}

// JB: A map storing all spawned actors based on their index in the PrefabAsset->ActorData array.
TMap<int32, AActor*> SpawnedActors;
// JB: A map storing all actor templates.
TMap<int32, AActor*> Templates;
// JB: A map storing all spawned actors based on their ItemID.
TMap<FGuid, AActor*> ChildActors;
// JB: The index used to access the correct ActorData.
int32 ActorIndex = 0;
for (FPrefabricatorActorData& ActorItemData : PrefabAsset->ActorData) {
// Handle backward compatibility
{
Expand Down Expand Up @@ -665,10 +687,11 @@ void FPrefabTools::LoadStateFromPrefabAsset(APrefabActor* PrefabActor, const FPr
}
}

// JB: We first spawn the actors as placeholders only.
AActor* Template = nullptr;
if (!ChildActor) {
TSharedPtr<IPrefabricatorService> Service = FPrefabricatorService::Get();
if (Service.IsValid()) {
AActor* Template = nullptr;
if (InState.IsValid()) {
TWeakObjectPtr<AActor>* SearchResult = InState->PrefabItemTemplates.Find(ActorItemData.PrefabItemID);
if (SearchResult) {
Expand All @@ -680,18 +703,6 @@ void FPrefabTools::LoadStateFromPrefabAsset(APrefabActor* PrefabActor, const FPr
}

ChildActor = Service->SpawnActor(ActorClass, FTransform::Identity, PrefabActor->GetLevel(), Template);
if (!Template) {
LoadActorState(ChildActor, ActorItemData, InSettings);
if (InState.IsValid()) {
InState->PrefabItemTemplates.Add(ActorItemData.PrefabItemID, ChildActor);
InState->_Stat_SlowSpawns++;
}
}
else {
if (InState.IsValid()) {
InState->_Stat_FastSpawns++;
}
}
}
}
else {
Expand All @@ -701,29 +712,56 @@ void FPrefabTools::LoadStateFromPrefabAsset(APrefabActor* PrefabActor, const FPr
}

if (ChildActor) {
ParentActors(PrefabActor, ChildActor);
AssignAssetUserData(ChildActor, ActorItemData.PrefabItemID, PrefabActor);
// JB: Fills the data maps with the proper references.
SpawnedActors.Add(ActorIndex, ChildActor);
Templates.Add(ActorIndex, Template);
ChildActors.Add(ActorItemData.PrefabItemID, ChildActor);
}
ActorIndex++;
}

// Set the transform
FTransform WorldTransform = ActorItemData.RelativeTransform * PrefabActor->GetTransform();
// JB: Now we load the saved actor data into the actors.
for (auto& SpawnedActorElement : SpawnedActors)
{
FPrefabricatorActorData& ActorItemData = PrefabAsset->ActorData[SpawnedActorElement.Key];
AActor* ChildActor = SpawnedActorElement.Value;
AActor* Template = Templates[SpawnedActorElement.Key];

if (!Template) {
LoadActorState(ChildActor, ActorItemData, InSettings, ChildActors);
if (InState.IsValid()) {
InState->PrefabItemTemplates.Add(ActorItemData.PrefabItemID, ChildActor);
InState->_Stat_SlowSpawns++;
}
}
else {
if (InState.IsValid()) {
InState->_Stat_FastSpawns++;
}
}
ParentActors(PrefabActor, ChildActor);
AssignAssetUserData(ChildActor, ActorItemData.PrefabItemID, PrefabActor);

// Set the transform
FTransform WorldTransform = ActorItemData.RelativeTransform * PrefabActor->GetTransform();
if (ChildActor->GetRootComponent()) {
EComponentMobility::Type OldChildMobility = EComponentMobility::Movable;
if (ChildActor->GetRootComponent()) {
EComponentMobility::Type OldChildMobility = EComponentMobility::Movable;
if (ChildActor->GetRootComponent()) {
OldChildMobility = ChildActor->GetRootComponent()->Mobility;
}
ChildActor->GetRootComponent()->SetMobility(EComponentMobility::Movable);
ChildActor->SetActorTransform(WorldTransform);
ChildActor->GetRootComponent()->SetMobility(OldChildMobility);
OldChildMobility = ChildActor->GetRootComponent()->Mobility;
}
ChildActor->GetRootComponent()->SetMobility(EComponentMobility::Movable);
ChildActor->SetActorTransform(WorldTransform);
ChildActor->GetRootComponent()->SetMobility(OldChildMobility);
}

if (APrefabActor* ChildPrefab = Cast<APrefabActor>(ChildActor)) {
if (InSettings.bRandomizeNestedSeed && InSettings.Random) {
// This is a nested child prefab. Randomize the seed of the child prefab
ChildPrefab->Seed = FPrefabTools::GetRandomSeed(*InSettings.Random);
}
if (InSettings.bSynchronousBuild) {
LoadStateFromPrefabAsset(ChildPrefab, InSettings, InState);
}
// JB: TODO: spawn all nested prefab actors already above in order to allow referencing them.
if (APrefabActor* ChildPrefab = Cast<APrefabActor>(ChildActor)) {
if (InSettings.bRandomizeNestedSeed && InSettings.Random) {
// This is a nested child prefab. Randomize the seed of the child prefab
ChildPrefab->Seed = FPrefabTools::GetRandomSeed(*InSettings.Random);
}
if (InSettings.bSynchronousBuild) {
LoadStateFromPrefabAsset(ChildPrefab, InSettings, InState);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ APrefabActor* UPrefabricatorBlueprintLibrary::SpawnPrefab(const UObject* WorldCo
{
APrefabActor* PrefabActor = nullptr;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
if (World) {
// JB: Added check if the Prefab is not nullptr.
if (World && Prefab) {
if (Prefab->bReplicates) {
FActorSpawnParameters SpawnParams;
PrefabActor = World->SpawnActor<AReplicablePrefabActor>(AReplicablePrefabActor::StaticClass(), Transform);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ struct PREFABRICATORRUNTIME_API FPrefabricatorPropertyAssetMapping {
UPROPERTY()
FName AssetObjectPath;

// JB: The ItemID of the referenced ID.
UPROPERTY()
FGuid AssetItemID;

UPROPERTY()
bool bUseQuotes = false;
};
Expand All @@ -36,7 +40,8 @@ class PREFABRICATORRUNTIME_API UPrefabricatorProperty : public UObject {
TArray<FPrefabricatorPropertyAssetMapping> AssetSoftReferenceMappings;

void SaveReferencedAssetValues();
void LoadReferencedAssetValues();
// JB: The InChildActors map contains all actors spawned by the prefab stored by their ItemID.
void LoadReferencedAssetValues(const TMap<FGuid, AActor*>& InChildActors);
};

USTRUCT()
Expand Down
3 changes: 2 additions & 1 deletion Source/PrefabricatorRuntime/Public/Prefab/PrefabTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class PREFABRICATORRUNTIME_API FPrefabTools {

private:
static void SaveActorState(AActor* InActor, APrefabActor* PrefabActor, FPrefabricatorActorData& OutActorData);
static void LoadActorState(AActor* InActor, const FPrefabricatorActorData& InActorData, const FPrefabLoadSettings& InSettings);
// JB: The InChildActors map contains all actors spawned by the prefab stored by their ItemID.
static void LoadActorState(AActor* InActor, const FPrefabricatorActorData& InActorData, const FPrefabLoadSettings& InSettings, const TMap<FGuid, AActor*>& InChildActors);

};

Expand Down