Unreal does not seem to save any components added to an Actor constructed from C++.
I am currently writing a plugin, in which I dynamically add Actors to the Editor World, based on JSON files. Each JSON file describes a tree-mapped hierarchy (scenegraph), which the plugin will add to the editor. The Actor is used for transforming the root node. While, its components extend and visualize the scenegraph.
When I save the project and reopen it, I can see the new Actors in the World Outliner, but all of its components are gone. Here are two screenshots. The first one is before closing the project. The second one after reopening the saved state.
Maybe I am just missing something, but I believe this might be a bug, occurring because I am using NewObject, instead of
CreateDefaultSubobject. For test purposes, I had previously parsed a static JSON File inside the constructor of my Actor, which gave me access to the FObjectInitializer and therefore CreateDefaultSubobject. Back then saving the actor would also save its components. Since I could not find a way to hand in the JsonDocument upon construction, I had to switch to creating the components after constructing the Actor, which implies use of NewObject. (I have also tried the depricated versions NewNamedObject & ConstructObject, without success)
I am sorry for the huge wall of code that is about to follow, but I wanted to make sure that whoever will help me solve this problem would have enough information to help.
bool JsonLevelComposer::CreateScene(TSharedPtr<FJsonObject> const& JsonObject)
{
UWorld* World = GEditor->GetEditorWorldContext().World();
if (!World)
return false;
MyScene_ = Cast<AMyScene*> GEditor->AddActor(
World->GetCurrentLevel(),
AMyScene::StaticClass(),
FTransform(FVector(0))
);
USceneComponent* Root = CreateSceneComponent(JsonObject, MyScene_->GetRootComponent());
FString Name = Root->GetName();
UE_LOG(LogActor, Warning, TEXT("Added scene with root ~> %s"), *Name);
if (Root == nullptr)
return false;
if (JsonObject->HasField(FString("Children")))
CreateChildren(JsonObject.Get()->GetArrayField(FString("Children")), Root);
return true;
}
void JsonLevelComposer::CreateChildren(
TArray<TSharedPtr<FJsonValue>> const& JsonChildren,
USceneComponent* Parent)
{
for (int i = 0; i < JsonChildren.Num(); ++i)
{
auto JsonObject = JsonChildren[i].Get()->AsObject();
if (JsonObject.IsValid())
{
USceneComponent* Child = CreateSceneComponent(JsonObject, Parent);
if (Child != nullptr)
{
if (JsonObject->HasField(FString("Children")))
CreateChildren(
JsonObject.Get()->GetArrayField(FString("Children")),
Child
);
}
}
}
}
USceneComponent* JsonLevelComposer::CreateSceneComponent(
TSharedPtr<FJsonObject> const& JsonObject,
USceneComponent* Parent)
{
USceneComponent* SceneComponent = nullptr;
/*
* JSON parsing for type of component is done here
* for simplicity of this example we'll just assume all
* components are just USceneComponents
*/
SceneComponent = NewObject<USceneComponent>(
MyScene_,
FName(*name)
);
SceneComponent->RegisterComponent();
SceneComponent->AttachTo(Parent);
// parses and applies relative Transformation for this USceneComponent
CreateComponentTransform(JsonObject, SceneComponent);
return SceneComponent;
}
EDIT I:
Here is how you can easily reproduce the issue.
I. Trigger the function below from inside the editor (I am using a button callback connected to a button, which my plugin adds to the editor toolbar. There might be easier ways). This should add a StaticMeshActor, which intentionally does not hold a mesh. A StaticMeshComponent is added and attached to the Actors RootComponent. It holds a sphere mesh which should be visible at position 0,0,0.
II. Save the project. Then close the editor and open it again. Our StaticMeshActor will still be listed in the World Outliner, but does no longer hold the StaticMeshComponent⌠We grieve for the lost sphere.
Any ideas as to what destroyed our component?
void MakeActorWithComponent()
{
UWorld* world = GEditor->GetEditorWorldContext().World();
if (!world)
return;
AStaticMeshActor* MeshActor = (AStaticMeshActor*)GEditor->AddActor(
world->GetCurrentLevel(),
AStaticMeshActor::StaticClass(),
FTransform(FVector(0))
);
UStaticMesh* StaticMesh = Cast<UStaticMesh>(StaticLoadObject(
UStaticMesh::StaticClass(),
NULL,
TEXT("/Game/StarterContent/Props/MaterialSphere.MaterialSphere")
));
if (StaticMesh == nullptr)
return;
UStaticMeshComponent* MeshComponent = NewObject<UStaticMeshComponent>(
MeshActor,
TEXT("VanishingSphere")
);
MeshComponent->RegisterComponent();
MeshComponent->AttachTo(MeshActor->GetRootComponent());
MeshComponent->SetStaticMesh(StaticMesh);
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
EDIT II:
This edit shows how to solve the issue. I just added a call to AActor::AddInstanceComponent after construction of the component. Thanks mrooney for pointing this out. Here is a revised version of the simplified example
void MakeActorWithComponent()
{
/*
* see EDIT I
*/
UStaticMeshComponent* MeshComponent = NewObject<UStaticMeshComponent>(
MeshActor,
TEXT("SavedSphere")
);
MeshActor->AddInstanceComponent(MeshComponent);
MeshComponent->RegisterComponent();
MeshComponent->AttachTo(MeshActor->GetRootComponent());
MeshComponent->SetStaticMesh(StaticMesh);
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}