keywords: Unreal Editor Programming, Create components in Blueprint Editor, C++ property changed, property modification

Method 1st

Construct component using C++ property that value edit in blueprint.

Header:

UBoxComponent* BoxComp;
    
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = AI)
FVector BoxSize;

void OnConstruction(const FTransform& Transform) override;

CPP:

AMyActor::AMyActor()
{
    BoxComp = nullptr;
    BoxSize = FVector::ZeroVector;
}

void AMyActor::OnConstruction(const FTransform& Transform)
{
    Super::OnConstruction(Transform);
    
    BoxComp = NewObject<UBoxComponent>(this);
    if(BoxComp)
    {
        BoxComp->RegisterComponent();
        BoxComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
        BoxComp->SetBoxExtent(BoxSize);
    }
}

Thus, We can modify the size of component in UE4Editor.

Method 2nd

overwirte function PostEditChangeProperty of AActor:

#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
Attention

1, How to Detect if PostEditChangeProperty is called from Level editor or classDefault/blueprint editor:

UObjectBaseUtility::IsTemplate();

Reference:
https://answers.unrealengine.com/questions/566843/how-to-detect-if-posteditchangeproperty-is-called.html

2, Why Component is not visible in level editor when modify Transform in PostEditChangeProperty() callback?

Reason:
Super::PostEditChangeProperty() hasn’t been called in Your PostEditChangeProperty() function.

Solution:
Add Super::PostEditChangeProperty().

example
header:

UPROPERTY(EditAnywhere, Category = ArrowTransform)
    FVector RelativeLoc;

UPROPERTY(EditAnywhere, Category = ArrowTransform)
    FRotator RelativeRot;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    UArrowComponent* TestComponent;

cpp:

void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
    Super::PostEditChangeProperty(PropertyChangedEvent);
    
    const FString PropertyName = PropertyChangedEvent.Property->GetName();
    if(PropertyName == TEXT("RelativeLoc"))
    {
        if (TestComponent)
        {
            TestComponent->SetRelativeLocation(RelativeLoc);
        }
    }
    else if(PropertyName == TEXT("RelativeRot"))
    {
        if (TestComponent)
        {
            TestComponent->SetRelativeRotation(RelativeRot);
        }
    }
}

void AMyActor::PostRegisterAllComponents()
{
    Super::PostRegisterAllComponents();
    
    if (TestComponent)
    {
        TestComponent->SetRelativeLocation(RelativeLoc);
        TestComponent->SetRelativeRotation(RelativeRot);
    }
}

If not override PostRegisterAllComponents(), modification of properties would not take effect when start Editor at first time.

In earlier version PostLoad() must be overridden if want to create component when edtior started, but now it doesn’t work.

How to create components in Blueprint Editor and Level Editor

Header:

UCLASS()
class TESTPROJ_API AMyActor : public AActor
{
    GENERATED_BODY()
    
protected:

    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Config")
    TArray<UStaticMeshComponent*> Components;

    virtual void PostRegisterAllComponents() override;

#if WITH_EDITOR
    virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif

    void RebuildComponents();

    UPROPERTY(EditAnywhere, Category = "Config")
    UStaticMesh* TestAsset;

    UPROPERTY(EditAnywhere, Category = "Config", meta=(ClampMin = "1", ClampMax = "10", UIMin = "1", UIMax = "10"))
    int32 Counts = 3;

    UPROPERTY(EditAnywhere, Category = "Config", meta=(ClampMin = "0.0", ClampMax = "10000.0", UIMin = "0.0", UIMax = "10000.0"))
    float Padding = 200.f;
};

CPP:

void AMyActor::PostRegisterAllComponents()
{
    Super::PostRegisterAllComponents();

    RebuildComponents();
}

#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
    Super::PostEditChangeProperty(PropertyChangedEvent);
    
    const FString Name = PropertyChangedEvent.Property->GetName();
    if(Name == TEXT("Counts") || Name == TEXT("TestAsset") || Name == TEXT("Padding") )
    {
        RebuildComponents();
    }
}
#endif

void AMyActor::RebuildComponents()
{
    //must cache location before destory component, otherwise GetActorLocation() return (0, 0, 0) when paly game,
    //because the RootComponent has been destroyed.
    const FVector ActorLoc = GetActorLocation();
    
    for(UStaticMeshComponent* Component : Components)
    {
        if(Component)
        {
            Component->DestroyComponent();
        }
    }

    Components.Reset(Counts);
    bool bRootComponentSet = false;
    for(int32 i = 0 ; i < Counts; i++)
    {
        UStaticMeshComponent* Component = NewObject<UStaticMeshComponent>(this);
        if(GetWorld())
        {
            Component->RegisterComponent();
        }
        if(!bRootComponentSet)
        {
            bRootComponentSet = true;
            this->SetRootComponent(Component);
        }
        else
        {
            Component->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale);    
        }
        Component->SetStaticMesh(TestAsset);
        const FVector Loc =  ActorLoc + FVector(0.f, 0.f, i * Padding);
        Component->SetWorldLocation(Loc);
        Components.Add(Component);
    }
}

失望并不可怕,可怕的是一次次失望过后,平静的安慰自己,心存侥幸试着继续相信。