Why is it not possible to set an AAIController as the default player controller class?

Hi. I’m trying to make a game with top down click camera and character movement and when I programmatically try to change the PlayerControllerClass in my game mode it compiles, but when I run the game it gives me an error saying “Couldn’t spawn player: Failed to spawn player controller”. It’s the same if I make my own custom class derived from AAIController, but it works with an APlayerController and classes derived from it. I’m interested in doing this all in C++ so the numerous threads about using blueprints don’t help me. Do I have to spawn a new AAIController and unpossess the default player controller and possess my custom pawn with the manually spawned AAIController? I tried doing that and then my character lost all the input listening functionality so I’m really lost. Any help would be appreciated, thanks.

Oh and I’m using 4.15.0 if that matters.

Re title: it’s because the default player controller class has to be of type APlayerController as you said. AAIController isn’t APLayerController. They both have the same parent-class (AController) but they are siblings themselves. If the default player controller class variable had the type of AController then it would work, but since it requires APlayerController type you can’t set the value to be AAIController, only to APlayerController as you said. It shouldn’t even compile if you try to set it to AIController.

You don’t need to spawn AIControllers yourself, you can set the pawn’s default AIController class as well and set a property that automatically creates an AIController for it when the pawn is spawned into the world.

I use this to set the AIController for my spawned pawns (I’m not sure if this helps)

AAlienGravityPawn * AAlienGravityPawn::SpawnPawn(TSubclassOf<AAlienGravityPawn> &cls, class ATileMaster *owner, const FVector& location, const FRotator &rotation)
{
FTransform SpawnTM(rotation, location);

AAlienGravityPawn *pawn = Cast&lt;AAlienGravityPawn&gt;(UGameplayStatics::BeginDeferredActorSpawnFromClass(owner, cls, SpawnTM));
if (pawn)
{
	pawn-&gt;SetOwner(owner);
	pawn-&gt;AIControllerClass = AAiGravityController::StaticClass();
	UGameplayStatics::FinishSpawningActor(pawn, SpawnTM);
	if (pawn-&gt;IsPendingKill())
		return NULL;
	pawn-&gt;SpawnDefaultController();
}
return pawn;

}

Thank you both so much. I think I understand how it’s supposed to be done now. I assume I can put


AIControllerClass = AAzurePlayerController::StaticClass();

in the constructor of my character and then call



AAzureCharacter *PlayerCharacter = World->SpawnActor<AAzureCharacter>(AAzureCharacter::StaticClass());
PlayerCharacter->SpawnDefaultController();


to spawn a character with an AI controller? But after I do that how do I access the AI controller? Do I use GetController() and cast it to an AAzurePlayerController pointer? I tried doing that with static_cast and it compiled but crashed the game when I tried to dererference its functions. Still I feel like I’ve made some progress. Thanks for pointing me in the right direction. Oh and AAzurePlayerController is derived from AAIController by the way, I double and triple checked that.

So a follow up question if you don’t mind. Now that I’ve spawned my character with an AI controller, how do I access the actual AI controller and use it programmatically? I’m specifically interested in the MoveToLocation and MoveToActor functions.

1 Like

Don’t use


static_Cast

use


Cast<>()

Also, you should always check the results of a cast with an if, or an assertion. I prefer Assertions usually so I know if somethings gone wrong straight away.

SpawnDefaultController() will spawn the controller specified by the Pawn (AIControllerClass) - NOT the GameMode.

I gave it a try and UE4’s cast function returns a nullptr, I assume that means that my character controller isn’t being properly changed into the right class? Here’s the code I have to spawn the character and the controller:



	LocalController = GEngine->GetFirstLocalPlayerController(World);
	LocalController->UnPossess();
	AAzureCharacter *PlayerCharacter = World->SpawnActor<AAzureCharacter>(AAzureCharacter::StaticClass());
	PlayerCharacter->AIControllerClass = AAzureCharacter::StaticClass();
	PlayerCharacter->SpawnDefaultController();


And this is the code where I try to cast the controller to my custom one derived from AAIController:



void AAzureCharacter::MoveCharacter()
{

	FVector WorldLocation, WorldDirection;
	if(LocalController->DeprojectMousePositionToWorld(WorldLocation, WorldDirection))
	{
		AAzurePlayerController *Ctrl = Cast<AAzurePlayerController>(GetController());
		if(Ctrl == nullptr)
			DebugMessage("AI controller is nullptr");
		else
			DebugMessage("AI controller isn't nullptr");
		//Controller->MoveToLocation(WorldLocation);
	}

}


DebugMessage is a simple wrapper to output text to screen that I wrote, and it always prints “AI controller is nullptr”

So the issue seems to be that the right controller isn’t spawned, which is odd considering I’ve done what I was told. Any idea where to go from here? :frowning:

If the cast returns nullptr then it means the cast failed (no different to how static_cast would), meaning you’re not using your custom controller class.

Also, your code for setting the controller type is wrong:



PlayerCharacter->AIControllerClass = AAzureCharacter::StaticClass();


Should surely be:



PlayerCharacter->AIControllerClass = AAzureAIController::StaticClass();


Strictly speaking, that probably shouldn’t even compile.

Oops, well that’s a silly mistake. Thanks for pointing it out! But I still can’t get it to work. I changed my approach to testing if it’s possible to cast the controllers right away after spawning the character and the controller itself. I autopossess the character in the constructor with AutoPossessPlayer = EAutoReceiveInput::Player0; if that makes a difference. Anyways here’s the code I used to test it:



	LocalController = GEngine->GetFirstLocalPlayerController(World);
	LocalController->UnPossess();
	AAzureCharacter *PlayerCharacter = World->SpawnActor<AAzureCharacter>(AAzureCharacter::StaticClass());
	PlayerCharacter->AIControllerClass = AAzurePlayerController::StaticClass();
	PlayerCharacter->SpawnDefaultController();
	AAzurePlayerController *Ctrl = Cast<AAzurePlayerController>(PlayerCharacter->Controller);
	if(Ctrl == nullptr)
		DebugMessage("Controller is nullptr");
	else
		DebugMessage("Controller isn't nullptr");


And it returns nullptr. But if I change it to:



	PlayerCharacter->AIControllerClass = APlayerController::StaticClass();
	PlayerCharacter->SpawnDefaultController();
	APlayerController *Ctrl = Cast<APlayerController>(PlayerCharacter->Controller);


or



	PlayerCharacter->AIControllerClass = AController::StaticClass();
	PlayerCharacter->SpawnDefaultController();
	AController *Ctrl = Cast<AController>(PlayerCharacter->Controller);


It returns not nullptr. So I tried changing it to the default AI controller:



	PlayerCharacter->AIControllerClass = AAIController::StaticClass();
	PlayerCharacter->SpawnDefaultController();
	AAIController *Ctrl = Cast<AAIController>(PlayerCharacter->Controller);


And then it returns nullptr again. It seems like it’s either failing to assign an AAIController or a derived class from it as the default controller, or the cast isn’t working. I’m absolutely lost and have no idea what to do :frowning:

AAzureCharacter is derived from ACharacter by the way, if that’s relevant. Is it possible to spawn a character controller manually and set it to PlayerCharacter->Controller manually and go that path maybe? I tried doing that before but the camera seemed to unhinge from the character object and all inputs were lost when I possessed my character with the manually spawned controller.

My Suggestion would be to drop breakpoints in. SpawnDefaultController() is obviously failing for some reason, so drop a breakpoint there and step through that function to see why it’s failing.

I figured out what the problem is, but now I have even bigger problems. Apparently I couldn’t spawn an AAzurePlayerController when I used autopossess in the constructor like so AutoPossessPlayer = EAutoReceiveInput::Player0;

Here’s the code that “works”:



	LocalController = GEngine->GetFirstLocalPlayerController(World);
	AAzureCharacter *PlayerCharacter = World->SpawnActor<AAzureCharacter>(AAzureCharacter::StaticClass());
	PlayerCharacter->AIControllerClass = AAzurePlayerController::StaticClass();
	PlayerCharacter->SpawnDefaultController();
	AAzurePlayerController *Ctrl = Cast<AAzurePlayerController>(PlayerCharacter->Controller);
	if(Ctrl == nullptr)
		DebugMessage("Controller is nullptr");
	else
		DebugMessage("Controller isn't nullptr");


And here are the results with autopossess:

And without autopossess:

Now that I’ve successfully spawned the AI controller I’ve lost all axis mapping and action mapping inputs so none of my mouse movement or clicks are registering. Not to mention the camera which is supposed to be attached to the character object itself using a spring arm is now displaying a view straight from the character itself, rather than giving the nice top down view.

Here’s how I set up my camera:



In constructor:
	CameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera"));
...]
void AAzureCharacter::EnableTopDownCamera()
{

	CameraSpringArm->AttachTo(RootComponent);
	CameraSpringArm->TargetArmLength = CameraOffset;
	CameraSpringArm->SetWorldRotation(FRotator(-60.f, 0.f, 0.f));
	Camera->AttachTo(CameraSpringArm, USpringArmComponent::SocketName);

}


And this is the code for the player inputs:



void AAzureCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAction("LeftMouse", IE_Pressed, this, &AAzureCharacter::MoveCharacter);
	PlayerInputComponent->BindAction("RightMouse", IE_Pressed, this, &AAzureCharacter::SetRightMouseDown);
	PlayerInputComponent->BindAction("RightMouse", IE_Released, this, &AAzureCharacter::SetRightMouseUp);
	PlayerInputComponent->BindAxis("MouseX", this, &AAzureCharacter::RotateCameraXAxis);
}


Whenever I touch something something immediately breaks, I don’t know what to do :frowning:

Use the Object Initializer version of the constructor, it’s much better and you get weird ownership issues by not using it sometimes.

My advice at this stage would be to download a content example and study the source code, such as the ShooterGame example etc. Again it’s probably a nullptr error, or something isn’t bound correctly etc. Usually the output window in VS gives you an idea of what went wrong too.

I’m posting for future reference to say that I figured out the solution(a while ago now but still). I assigned the pre-existing player controller to a custom made spectator pawn that I attached to my character pawn(AAzureCharacter) and spawned the AI controller simlarly to above. It’s working as intended now although I have to receive delegate calls in the spectator pawn and then call the AI controller functions in the character, which is its parent or so to speak.


	LocalController = GEngine->GetFirstLocalPlayerController(World);
	AAzureCharacter *PlayerCharacter = World->SpawnActor<AAzureCharacter>(AAzureCharacter::StaticClass());
	PlayerCharacter->AIControllerClass = AAzurePlayerController::StaticClass();
	PlayerCharacter->SpawnDefaultController();
	DummyPawn = World->SpawnActor<AAzurePlayerDummy>(AAzurePlayerDummy::StaticClass());
	DummyPawn->Initialize(PlayerCharacter); //Custom function that binds the dummy to the character