Description

Non-instanced object ref properties are incorrectly being skipped at property initialization time when Blueprint class instances are spawned with the fast property initialization path enabled.

This results in a subobject instance that's unique per Blueprint class instance, so it behaves as if it were instanced. It should instead be referencing the subobject instance from the default data (parent CDO), in order to match what happens when the fast path cannot be taken (e.g. when spawning child actor components).

Steps to Reproduce
  1. Create a new blank C++ project.
  2. Add a new C++ class derived from Object (UMySubobjectType).
  3. Update the MySubobjectType.h class header as follows:
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "MySubobjectType.generated.h"
    
    /**
     * 
     */
    UCLASS(BlueprintType)    // ADD 'BlueprintType'
    class UE_191334_API UMySubobjectType : public UObject
    {
    	GENERATED_BODY()
    	
    };
    
  1. Add a new C++ class derived from Actor (AMyTestActor).
  2. Update the MyTestActor.h class header as follows:
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "MySubobjectType.h"                 // ADD THIS INCLUDE
    #include "MyTestActor.generated.h"
    
    UCLASS()
    class UE_191334_API AMyTestActor : public AActor
    {
    	GENERATED_BODY()
    	
    public:	
    	// Sets default values for this actor's properties
    	AMyTestActor();
    
            // ADD THIS PROPERTY
    	UPROPERTY(EditAnywhere, BlueprintReadWrite)
    	TObjectPtr<UMySubobjectType> MySubobjectInstance;
    
    protected:
    	// Called when the game starts or when spawned
    	virtual void BeginPlay() override;
    
    public:	
    	// Called every frame
    	virtual void Tick(float DeltaTime) override;
    
    };
    
  3. Add to the default constructor for the class in the .cpp file:
    // Sets default values
    AMyTestActor::AMyTestActor()
    {
     	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    	PrimaryActorTick.bCanEverTick = true;
    
            // ADD THIS LINE
    	MySubobjectInstance = CreateDefaultSubobject<UMySubobjectType>("MySubobject");
    }
    
  4. Compile in VS, and relaunch the editor / live coding reload.
  5. Create a new Blueprint class based on the C++ class created above (BP_MyTestActor).
  6. Implement the event graph as shown in the screen shot.
  7. Compile and save the Blueprint class.
  8. Create another new Blueprint class based on Actor (BP_MyTestActor_Spawner).
  9. Add a new child actor component, and set the child actor class to BP_MyTestActor.
  10. Compile and save the Blueprint class.
  11. Drag a new instance of each class into the current level and save (NewMap).
  12. Start PIE, and note the output that's shown in the log. The bracketed name represents the name of the BP instance, and the second is the name of the owner of the subobject instance:
    LogBlueprintUserMessages: [BP_MyTestActor_C_1] BP_MyTestActor_C_1
    LogBlueprintUserMessages: [ChildActor_GEN_VARIABLE_BP_MyTestActor_C_CAT_192] ChildActor_GEN_VARIABLE_BP_MyTestActor_C_CAT
    
  13. Expected result: In both cases, the owner should report as Default__MyTestActor.
  14. Close the editor.
  15. Now change the UPROPERTY line in MyTestActor.h to include the 'Instanced' keyword:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced)
    
  16. Compile in VS, and relaunch the editor.
  17. Reopen NewMap, and start PIE. Note the output again in the log:
    LogBlueprintUserMessages: [BP_MyTestActor_C_1] BP_MyTestActor_C_1
    LogBlueprintUserMessages: [ChildActor_GEN_VARIABLE_BP_MyTestActor_C_CAT_176] ChildActor_GEN_VARIABLE_BP_MyTestActor_C_CAT_176
    

Additional notes:

  • Note that the output for the BP_MyTestActor instance is unchanged regardless of whether or not 'Instanced' is specified. This is unexpected for the non-instanced case; that should instead show the owner as Default__MyTestActor. This means that there is currently no difference between instanced and non-instanced subobjects if we are using the fast path for post-construct property initialization.
  • For the "spawner" (child actor) case, the fast path cannot be used when spawning the child actor using the component template (ChildActor_GEN_VARIABLE_BP_MyTestActor_C_CAT). Note that the output does change, and is correct in the 'Instanced' case. The non-instanced case is also technically correct; however, when the template itself is instanced that does use the fast path, so the template incorrectly ends up with its own instance, when it also should be referencing the instance that's owned by Default__MyTestActor (as above).
  • When we spawn the instance from the child actor template after starting PIE, the fast path cannot be taken here, so this shows that the spawned instance (correctly) ends up reporting the owner as the source template when we are using the slow path.

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-191334 in the post.

0
Login to Vote

Unresolved
ComponentUE - Gameplay - Blueprint
Affects Versions4.275.05.15.25.3
Target Fix5.5
CreatedJul 27, 2023
UpdatedFeb 12, 2024