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;
};