Code changes break blueprint

Hello everyone.

I have following class structure: Pawn → Character2D → Hero2D → Blueprint

Character2D class contains capsule component, flipbook component, custom movement component(derived from MovementComponent) and custom attack component(derived from ActorComponent).

They all declared in the header of Character2D class in the same way:

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	class UPaperFlipbookComponent *FlipbookComponent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	class UCapsuleComponent *CapsuleComponent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	class USimple2DMovement *MovementComponent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	class UAttackComponent *AttackComponent;

This is how ACharacter2D() constructor looks like:

ACharacter2D::ACharacter2D()
{
	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = true;

	CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("RootCapsule"));
	CapsuleComponent->bGenerateOverlapEvents = true;
	CapsuleComponent->SetNotifyRigidBodyCollision(true);
	CapsuleComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	CapsuleComponent->SetCollisionObjectType(ECC_Pawn);
	CapsuleComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
	CapsuleComponent->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);
	CapsuleComponent->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Overlap);
	CapsuleComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
	this->OnActorHit.AddDynamic(this, &ACharacter2D::OnHit);

	RootComponent = CapsuleComponent;

	FlipbookComponent = CreateDefaultSubobject<UPaperFlipbookComponent>(TEXT("FlipBook"));
	FlipbookComponent->SetupAttachment(RootComponent);
	FlipbookComponent->SetRelativeLocation(FVector::ZeroVector);

	MovementComponent = CreateDefaultSubobject<USimple2DMovement>(TEXT("2DMovement"));
	MovementComponent->UpdatedComponent = RootComponent;

	AttackComponent = CreateDefaultSubobject<UAttackComponent>(TEXT("Attack"));
}

The problem I’m facing is that when I change code of my movement component, It detaches from the blueprint.

The “Details” tab of the movement component in the blueprint editor gets empty, when I spawn new instance of the blueprint on a level, this is what I see:

219260-issue.jpg

But this movement component is declared as a UPROPERTY.

Any usual measures like rebuilding the project/reparenting the blueprint do not work.

The only thing that works is creating a new blueprint and copy/pasting everything from the previous one.

But the bigger the project gets, the more annoying this process is.

Another interesting detail is that changes in my attack component get reflected in the bluepring without any issues.

Here is a part of attack component header:

#pragma once

#include "CoreMinimal.h"
#include "Attack.h"
#include "Components/ActorComponent.h"
#include "AttackComponent.generated.h"

UCLASS()
class FRANCIS_API UAttackComponent : public UActorComponent
{
	GENERATED_BODY()
public:	
	UAttackComponent();
protected:
	virtual void BeginPlay() override;
public:
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	void Attack();
	FVector2D GetDash() const;

etc.

And a part of movement component header:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/MovementComponent.h"
#include "Engine.h"
#include "Simple2DMovement.generated.h"

struct F_Timer
{
	float CurrentTime, TargetTime;

	bool Expired()
	{
		return CurrentTime >= TargetTime;
	}

	F_Timer() : CurrentTime(0), TargetTime(0) {}
	F_Timer(const float Target) : CurrentTime(0), TargetTime(Target) {}
};

UCLASS()
class FRANCIS_API USimple2DMovement : public UMovementComponent
{
	GENERATED_BODY()
public:
	USimple2DMovement();
	virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
	virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent) override;
	void SetHit(const FHitResult &Hit);
	void AddDash(const FVector2D &Dash);

etc.

The only (probably) significant difference I see is a structure definition before movement component class declaration, but commenting of everything related to the structure does not solve the problem either.

Does anyone have any idea why movement component causes issues and attack component doesn’t?

Could you change your blueprint-parent constructor to include the FObjectInitializer? Assuming your blueprint derives from ACharacter2D, instead of using a parameterless constructor try:
ACharacter2D(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

And in your cpp:

ACharacter2D::ACharacter2D(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
    // Your normal constructor contents here
}

Since you derive blueprint from Hero2D, you’d need to do both that and ACharacter2D. I’m not sure if it’ll make a difference, but maybe the blueprint relies on some stuff from the FObjectInitializer argument.

Unfortunately, it hasn’t helped.

I think I’d be weird if it did, because the problem has to be somehow connected with the movement component.

Hmm, have you checked to see if newly placed ‘broken’ blueprints actually have a valid reference to an object of type USimple2DMovement in the MovementComponent var of Character2D? E.g. in Char2D’s BeginPlay

if (MovementComponent && MovementComponent ->IsA(USimple2DMovement::StaticClass()))
    		UE_LOG(LogTemp, Warning, TEXT("Was a 2DMoveComp!"));

Just checked it. Nope, an instance of broken blueprint does not have a valid USimple2DMovement component reference. However, if I create a Hero2D instance, it does contain a valid reference.
Yesterday I read something similar about the issue caused by hot reload. So probably from now on I’ll have to avoid using it and recompile my project with the editor closed.

I tried adding and removing variables to the Simple2DMovement without hot reload, it appears that changes are reflected in the blueprint without issues when I compile outside of the editor, so I think it’s solved.