AI Controller spawning and despawning without calling Possess event

Hello,

I’m working on a game and I want to add spawning enemies in waves. So far I had a working player and enemy, both with multiplayer support. When I started to add a spawning system, things started to get weird. I created an entity spawner class with the following functions:


// Sets default values
AEnemySpawner::AEnemySpawner() {
	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("DefaultSceneRoot"));
	SpawnVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("SpawnVolume"));
	SpawnVolume->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);

 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AEnemySpawner::BeginPlay() {
	Super::BeginPlay();
}

// Called every frame
void AEnemySpawner::Tick(float DeltaTime) {
	Super::Tick(DeltaTime);
	if (!everTicked) {
		for (int i = 0; i < 1; i++) {
			SpawnEntity();
		}
		everTicked = true;
	}

}

void AEnemySpawner::SpawnEntity() {
	if (GetLocalRole() == ROLE_Authority) {
		FVector loc = GetActorLocation();
		FRotator rot = GetActorRotation();

		ASpiderAI* ai = Cast<ASpiderAI>(GetWorld()->SpawnActor<ACharacter>(actorToSpawn, loc, rot));
		//ai->SpawnDefaultController();
		//ASpiderController* cont = GetWorld()->SpawnActor<ASpiderController>(ASpiderController::StaticClass(), loc, rot);
		//cont->Possess(ai);
	}
}

Basically what it should do is spawn an enemy from the selected blueprint when it ticks for the first time. This blueprint is based on the AEnemyAI class, which is based on the ACharacter class.
The problem is, that the character appears, but unfortunatelly without the controller. I’ve set the default controller in the blueprint and also tried every possible combination of spawning the controller via code and/or using autopossessm but the controller didn’t seem to spawn properly, so I started debugging.

I’ve tried to add some logging messages to the constructor and BeginPlay of the SpiderAI class and to the BeginPlay and Possess functions of the SpiderController class. Here is how they looked:

------- ASpiderAI -------
ASpiderAI::ASpiderAI() {
	UE_LOG(LogTemp, Warning, TEXT("ASpiderAI Constructor"))
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	//AIControllerClass = ASpiderController::StaticClass();
	
	playerCollisionDetection = CreateDefaultSubobject<USphereComponent>(TEXT("Player Collision Detection"));
	playerCollisionDetection->SetupAttachment(RootComponent);

	playerAttackCollision = CreateDefaultSubobject <USphereComponent>(TEXT("Player Attack Collision Detection"));
	playerAttackCollision->SetupAttachment(RootComponent);

	damageCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("Damage Collision Detection"));
	damageCollision->SetupAttachment(GetMesh(), "Body");
	
	healthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("Health Component"));
	AddOwnedComponent(healthComponent);

}

// Called when the game starts or when spawned
void ASpiderAI::BeginPlay() {
	Super::BeginPlay();
	spiderController = Cast<ASpiderController>(GetController());
	UE_LOG(LogTemp, Warning, TEXT("ASpiderAI BeginPlay %d"), spiderController)
	if (spiderController) {
		Init();
	}
}

void ASpiderAI::Init() {
	if (GetLocalRole() == ROLE_Authority) {
		UE_LOG(LogTemp, Warning, TEXT("INIT"))
		spiderController->GetPathFollowingComponent()->OnRequestFinished.AddUObject(this, &ASpiderAI::OnAIMoveCompleted);
		playerCollisionDetection->OnComponentBeginOverlap.AddDynamic(this, &ASpiderAI::OnPlayerDetectedOverlapBegin);
		playerCollisionDetection->OnComponentEndOverlap.AddDynamic(this, &ASpiderAI::OnPlayerDetectedOverlapEnd);

		playerAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ASpiderAI::OnPlayerAttackOverlapBegin);
		playerAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ASpiderAI::OnPlayerAttackOverlapEnd);

		damageCollision->OnComponentBeginOverlap.AddDynamic(this, &ASpiderAI::OnDealDamageOverlapBegin);

	}
	canAttackPlayer = false;
	animInstance = GetMesh()->GetAnimInstance();
}

------- ASpiderController -------

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

	UE_LOG(LogTemp, Warning, TEXT("SpiderController BeginPlay"))
	navigationArea = FNavigationSystem::GetCurrent<UNavigationSystemV1>(this);
	
	RandomPatrol();
	

}

void ASpiderController::OnPossess(APawn* pawn) {
	Super::OnPossess(pawn);
	UE_LOG(LogTemp, Warning, TEXT("SpiderController OnPossess"))

}

Now here is where it gets weird. If I uncomment these 2 lines in the spawner class, it just crashes, no matter what and blames it on the Super::Tick(DeltaTime). The cause of the crash is the cont->Possess(ai); line

		//ASpiderController* cont = GetWorld()->SpawnActor<ASpiderController>(ASpiderController::StaticClass(), loc, rot);
		//cont->Possess(ai);

The other situation that can happen, is when I leave everything commented as is and then it creates the Controller and calls the BeginPlay() function AFTER the BeginPlay in the SpiderAI class, but it does not call the Possess function and the controller doesn’t even appear in the World Outliner in the Editor. The same thing happens, when I call the SpawnDefaultController function.

The weird thing is, that if I only place an enemy into the world via the editor, it calls the constructor of the SpiderAI, then the Possess function followed by the Begin Play function in the Controller class and at last the BeginPlay function in the SpiderAI class.

I would really appreciate if someone would be able to help me out in what did I do wrong and/or point me into a direction. If any further info is needed, please write a comment and I’ll post a reply as soon as I can.

There’s this bit of code in PostInitializeComponents in Pawn.cpp

			const bool bPlacedInWorld = (World->bStartup);
			if ((AutoPossessAI == EAutoPossessAI::PlacedInWorldOrSpawned) ||
				(AutoPossessAI == EAutoPossessAI::PlacedInWorld && bPlacedInWorld) ||
				(AutoPossessAI == EAutoPossessAI::Spawned && !bPlacedInWorld))
			{
				SpawnDefaultController();
			}

i think most people set the AIController setting in their Pawn, then set AutoPossessAI to whatever is appropriate for their use scenario.

Yes, I know that part and I tried to set it to a spawn on spawn or placed in world and it spawns the controller, but the controller never possesses my pawn and (I don’t know if it does, but it seems like) it despawns immediately

put a breakpoint in SpawnDefaultController and then step through it to see what happens?