ActorComponent constructed from C++ not saved

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);
}
1 Like

This is kind of a non-answer, but I don’t think there’s a good way to get runtime components added as class defaults, and I’m not sure if that would even be a very safe thing to do. Maybe UE’s reflection system is robust enough to handle reflection data changing at runtime and saving defaults correctly based off of that, but I would not bank on it.

If you’re trying to save runtime data into objects you’ll probably have to include it in your json and initialize your data that way.

edit: If all you want to do is save your editor instance data into a blueprint, you can do it manually by clicking the “Edit Blueprint” dropdown and then selecting “Apply Instance changes to blueprint”.

Not sure if what I am doing would exactly be considered runtime. To be clear, I am adding all Actors, as well as Components only when I am in ‘edit’ mode (I hope that is the right term). So everything happens before BeginPlay. Note that Unreal offers an ‘Add Component’ button inside the Details window, which does what I want, just not from code.

It would still be at runtime because you’re generating it from a json file during runtime instead of generating your data before launch. If you change the json file when you enter the game it’s going to generate a completely different object than your default object that you’d have created in the editor no?

When you say launch, what exactly do you refer to? I am guessing the moment BeginPlay() is called, correct? In that case, no all my data is generated before launch. The JSON parsing is done only in the editor. The plugin holds access to a TCP server socket. Incomming messages are parsed as JSON files and the scene structure is replicated inside the editor world. This feature is editor exclusive and will not be accessible when the game is running. In other words, The plugin is only being used, as long as you could also drag and drop stuff from the GUI.

I edited my post, to make the bug a little easier to reproduce.

Sorry, I misunderstood what you were trying to do with your plugin. I thought you were using the JSON in the editor and then also in the game to regenerate your objects. I think I understand more now. See what I add below.

This isn’t a full answer, but something to look into. If you look in SSCSEditor::AddNewComponent it does some weird stuff with the component after it creates it, but the thing I think you want to look into calling specifically is MarkBlueprintAsStructurallyModified or MarkBlueprintAsModified. If you’re only trying to add components to some instances of blueprints that’s a different animal and you want to look into the second half of the AddNewComponent function where it calls the following stuff:

// Add to SerializedComponents array so it gets saved
ActorInstance->AddInstanceComponent(NewInstanceComponent);
NewInstanceComponent->OnComponentCreated();
NewInstanceComponent->RegisterComponent();

// Rerun construction scripts
ActorInstance->RerunConstructionScripts();

edit: Now that I think about it, you could probably just call the AddNewComponent function directly.

AActor::AddInstanceComponent was all I needed to add. Which makes total sense, given the fact that the components I am adding are not part of any of the actors properties. I find it a little surprising still, since the doc for AActor::RegisterComponent states:

Will also adds to outer Actor’s Components array, if not already present.

Which is essentially all AddInstanceComponent does. Maybe that is worth another bug report. :wink:

I edited my post one last time to add the function call. Thanks a lot for your help!

It’s a little confusing, but the RegisterComponent adds it to the actor’s current OwnedComponents array for if you want to mess with stuff and add components at runtime, but InstanceComponents is what the AddInstanceComponent adds to, which are and Instanced UPROPERTY, so they get exported with an individual instance.

awww yeah, I failed to notice the difference of ‘Actors outer’ VS ‘instanced’ components array, when reading the doc. Thanks for pointing that out again.

Thanks folks, I just hit a similar issue and SelectedActor->AddInstanceComponent(NewDataComponent) now causes everything to save correctly and persist through re-launch of the editor