Null UObject in BeginPlay after SpawnActor called

Context:

I wanted to use the movement and path finding that comes with an AI Controller. So i decided to set up a player controller that spawns a pawn. That pawn has its default ai controller set to my custom ai controller… which will react to user input by passing the input down into the ai controller. Here’s that code that takes a touch event, checks if PlayerPawn is null. If it is, then it spawns the actor (pawn)…

void APlayerPawnController::OnTouchBegin(ETouchIndex::Type FingerIndex, FVector Location)
{
	if (FingerIndex == ETouchIndex::Touch1)
	{
		// Get pawn that belongs to ai controller and run its move method

		// Go and get the mouse position of where the click occured.
		const FVector mouseLocation = MovementLogic->TransformMousePositionToScreenSpace(this);

		FActorSpawnParameters spawnParams;
		spawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		
		if (PlayerPawnClass)
		{
			const FRotator rotation = FRotator(0.0f, 90.f, -90.f);
			if (!PlayerPawn)
			{
				AActor* spawnedPawn = GetWorld()->SpawnActor(PlayerPawnClass.Get(), &mouseLocation, &rotation, spawnParams);
				PlayerPawn = Cast<APlayerPawn>(spawnedPawn);
			}

			if (PlayerPawn && PlayerPawn->GetController())
			{
				APlayerPawnAIController* controller = Cast<APlayerPawnAIController>(PlayerPawn->GetController());
				controller->CustomMoveToLocation(mouseLocation);
			}
		}
	}
}

Inside the BeginPlay of the pawn that is spawned is the following:

void APlayerPawn::BeginPlay()
{
	Super::BeginPlay();

	if (Movement)
	{
		Movement->AssignPawn(this);
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("BeginPlay: Movement is empty"));
	}

	SpawnDefaultController();

}

There are two interesting things happening here.

Firstly, Movement is null. Which is strange, because check out the constructor for the pawn:

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

	AIControllerClass = APlayerPawnAIController::StaticClass();

	SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("PlayerPawn_RootComponent")));
	CollisionComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("PlayerPawn_CollisionComponent"));

	Movement = CreateDefaultSubobject<UMovement>(TEXT("PlayerPawn_Movement"));

	CollisionComponent->AttachTo(GetRootComponent());
}

… this constructor is called when SpawnActor is run from the PlayerController… and yet when BeginPlay is run, Movement is now null… whereas CollisionComponent is not.

The second interesting thing is that regardless of calling SpawnDefaultController… when the code gets to this line:

if (PlayerPawn && PlayerPawn->GetController())
{
	APlayerPawnAIController* controller = Cast<APlayerPawnAIController>(PlayerPawn->GetController());
	controller->CustomMoveToLocation(mouseLocation);
}

it does not go inside. And that’s because the controller is null?

Question:

After studying various articles on object lifetimes, looking through the code and investigating into ue4 code, i am struggling to see why Movement is null on BeginPlay and why SpawnDefaultController seems to be doing nothing.

Side Notes:

Here is my movement class:

/**
*
*/
UCLASS()
class GAMETEST_API UMovement : public UObject
{
	GENERATED_BODY()

public:
	UMovement();

	// Denotes with what speed the actor will move with
	UPROPERTY()
		float Speed;

	UFUNCTION()
		void AssignPawn(APawn* pawn);

	// Denotes where the actor currently is. Z axis is zeroed for 2D
	UFUNCTION()
		FVector GetCurrentLocation();

	// Denotes where the actor's journey will end. Z axis is zeroed for 2D
	UFUNCTION()
		FVector GetLocationToMoveTo();

	// Sets where the actor's journey will end. Z axis is zeroed for 2D
	UFUNCTION()
		void SetLocationToMoveTo(FVector locationToMoveTo);

	UFUNCTION()
		void StopMoving();

	// Denotes that movement is now allowed to occur on the pawn.
	// You must first AllowMovement, before 
	UFUNCTION()
		void AllowMovement();

	// If Pawn is allowed to move, then the pawn will start its journey to the location set by SetLocationToMoveTo
	UFUNCTION()
		void ProcessMovement(float deltaTime);

	// Denotes when the actor is running movement logic. When the actor starts its journey, it sets this property to true. As soon as the actor has finished its journey, it sets this to false.
	UFUNCTION()
		bool IsCurrentlyMoving();

private:

	// Denotes when the actor is running movement logic. When the actor starts its journey, it sets this property to true. As soon as the actor has finished its journey, it sets this to false.
	UPROPERTY()
		bool IsCurrentlyMovingToLocation;
	
	// Denotes where the actor's journey will end
	UPROPERTY()
		FVector LocationToMoveTo;

	UPROPERTY()
		UMovementLogic* MovementLogic;

	UPROPERTY()
		APawn* Pawn;

};

I think what might be happening is that your UMovement object is being garbage collected. Use the UPROPERTY() decorator when you declare it to avoid undesired garbage collection. UObjects are always collected if not stored in a UProperty.
You can also try to crate it on BeginPlay directly by using NewObject() since you are not gonna use it before that anyways.

Thanks for your help. After moving my instantiation to the BeginPlay and using NewObject to allow for this, the object is not garbage collected. I wonder if it’s due to the fact that in construction, if the object is not attached to a node tree, then it is lost to the ether? I will have to look at some point, but for now… problem solved.