Download

UMAP Actor Property loosing value post construction

Assumption: All UPROPERTY are not GC and modified values are saved when saving the current level UMAP in the editor.

This is specific to pre BeginPlay() map content generation. Not post BeginPlay() saving current game state.

Example Code of lost data:



#pragma once
#include "GameFramework/Actor.h"
#include "ActorPropertyTest.generated.h"

DECLARE_LOG_CATEGORY_EXTERN(ActorPropertyTestLog, Log, All);

UCLASS(Blueprintable)
class SAVEMAPTEST_API AActorPropertyTest : public AActor
{
    GENERATED_BODY()

public:    
    AActorPropertyTest();

    virtual void BeginPlay() override;
    virtual void Tick(float DeltaTime) override;
#if WITH_EDITOR
    virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override;
#endif

    UPROPERTY()
    int32 EmptyProperty;
    UPROPERTY(BlueprintReadWrite, Category = ParameterTest)
    int32 RW;
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = ParameterTest)
    int32 VRW;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ParameterTest)
    int32 ERW;
    UPROPERTY(SaveGame, BlueprintReadWrite, Category = ParameterTest)
    int32 SRW;
    UPROPERTY(BlueprintReadOnly, Category = ParameterTest)
    int32 RO;
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ParameterTest)
    int32 VRO;
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ParameterTest)
    int32 ERO;
    UPROPERTY(SaveGame, BlueprintReadOnly, Category = ParameterTest)
    int32 SRO;    

    UFUNCTION(BlueprintCallable, Category = ParameterTest)
    void RandomizePropertys();
};




#include "ActorPropertyTest.h"

DEFINE_LOG_CATEGORY(ActorPropertyTestLog);

AActorPropertyTest::AActorPropertyTest() {
    PrimaryActorTick.bCanEverTick = true;

    EmptyProperty = -1;
    RW = -1;
    VRW = -1;
    ERW = -1;
    SRW = -1;
    RO = -1;
    VRO = -1;
    ERO = -1;
    SRO = -1;
}

void AActorPropertyTest::BeginPlay() {
    Super::BeginPlay();
    UE_LOG(ActorPropertyTestLog, Warning, TEXT("EmptyProperty: %d, RW: %d, VRW: %d, ERW: %d, SRW: %d, RO: %d, VRO: %d, ERO: %d, SRO: %d"), EmptyProperty, RW, VRW, ERW, SRW, RO, VRO, ERO, SRO);
}

void AActorPropertyTest::Tick(float DeltaTime) {
    Super::Tick(DeltaTime);
}

#if WITH_EDITOR
void AActorPropertyTest::PostEditChangeProperty(struct FPropertyChangedEvent& e) {
    RandomizePropertys();
}
#endif

void AActorPropertyTest::RandomizePropertys() {
    EmptyProperty = FMath::RandRange(1, 100);
    RW = FMath::RandRange(1, 100);
    VRW = FMath::RandRange(1, 100);
    ERW = FMath::RandRange(1, 100);
    SRW = FMath::RandRange(1, 100);
    RO = FMath::RandRange(1, 100);
    VRO = FMath::RandRange(1, 100);
    ERO = FMath::RandRange(1, 100);
    SRO = FMath::RandRange(1, 100);
}


Steps to follow to replicate results:

  1. Place the new C++ AActorPropertyTest or Blueprint with parent of AActorPropertyTest into world. The results are the same in both cases.
  2. Modify a variable to trigger RandomizePropertys().
  3. Run the game and review the Output Log.
  4. Save the UMAP.
  5. Open the same UMAP.
  6. Run the game again and review the Output Log.

It appears that UPROPERTY with only BlueprintReadWrite or SaveGame with BlueprintReadWrite will restore values to default when saving a UMAP. BlueprintReadOnly works in all cases.

Is this the intended behavior?

This is a simplified example of the problem with only integers but it extends beyond a single datatype.