Download

In PACKAGE mode components disappear after load game

Hello all,

**Context: **
We have built a game and implemented a generic Save system in C++, where the state of each and every actor within the World who implements a custom interface (ISaveable), will be captured in the following structure (APPENDIX A), and added to the SaveGame instance. Everything works perfect when we test it in editor (Playing in Editor)

Problem:
After we package the project (Win 64) and we start the game, we perform a save and then a re-load. All the actors who implement ISaveable disappear (or at least their geometry is no longer visible). This happens ONLY in package mode!

Investigation:
I’ve did some debugging, and the values which we save are something that we also read back (so serialization / deserialization works fine). The problem appears to be in the Appendix C, the code highlighted in red: when we restore the transformation of inner components within the saveable actor. (I mean if we comment out that block, the actors / their geometry no longer disappears during load).

We spent a lot of time trying to understand what’s going on, but we’re out of clues now…
We are running on version 4 19.2

Any thoughts are highly appreciated! thank you!

APPENDIX A: Save structure declaration



USTRUCT()
struct FActorSave
{
    GENERATED_BODY()

    UPROPERTY(SaveGame)
        FName ActorInstanceName;

    UPROPERTY(SaveGame)
        FString ActorClassName;

    UPROPERTY(SaveGame)
        FTransform ActorTransformation;

    UPROPERTY(SaveGame)
        TMap<FName, FTransform> ComponentsTransform;

    UPROPERTY(SaveGame)
        TArray<uint8> ActorData;

public:
    FActorSave();
    FActorSave(AActor* Actor);
    void Deserialize(AActor* Actor);
};


APPENDIX B: This is how we fill in the FActorSave struct



FActorSave::FActorSave(AActor* Actor)
{
    check(Actor != nullptr);
    this->ActorClassName = Actor->GetClass()->GetName();
    this->ActorInstanceName = Actor->GetFName();
    this->ActorTransformation = Actor->GetActorTransform();

    // We collect the transformation for each nested mesh
    TArray<UStaticMeshComponent*> comps;
    Actor->GetComponents(comps);

    for (auto it = comps.CreateIterator(); it; ++it)
    {
        auto comp = *it;
        verify(!this->ComponentsTransform.Contains(comp->GetFName()));

        this->ComponentsTransform.Add(comp->GetFName(), comp->GetRelativeTransform());
    }


    FMemoryWriter ActorWriter = FMemoryWriter(this->ActorData, true);
    FActorSaveArchive Archive(ActorWriter);
    Actor->Serialize(Archive);
    ActorWriter.Flush();
}


APPENDIX C: This is how we deserialize the FActorSave data back into the Acotr instance during load



void FActorSave::Deserialize(AActor* Actor)
{
    check(Actor != nullptr);
    verify(Actor->GetFName().IsEqual(this->ActorInstanceName));

    FMemoryReader ActorReader = FMemoryReader(this->ActorData, true);
    FActorSaveArchive Archive(ActorReader);
    Actor->Serialize(Archive);
    ActorReader.Flush();

    Actor->SetActorTransform(this->ActorTransformation);

    // We restore the collected transformation for each nested object, provided we can find it by name
    TArray<UStaticMeshComponent*> comps;
    Actor->GetComponents(comps);

**   for (auto it = comps.CreateIterator(); it; ++it)
    {
        auto comp = *it;
        if (this->ComponentsTransform.Contains(comp->GetFName()))
        {
            comp->SetRelativeTransform(this->ComponentsTransform.FindRef(comp->GetFName()));
        }
    }**
}


Ultimately I found the issue. What I noticed is that in package mode, BeginPlay appears to be called before the location of all actors within the world is set. This led for the snippet of code highlighted in red to execute before the initial position of actors is set, essentially throwing them outside the FoV. Scheduling this sequence to execute on the next tick (GetWorldTimerManager().SetTimerForNextTick()) solved it.

thats great to know about the begin play solving issue