How to spawn random blueprint actor from list in c++

Hi Everyone,

I am working on a C++ version of the infinite runner blueprint tutorial. Things are going well, but I am running into a problem when it comes to spawning various tiles. What happens (theoretically) is tiles are represented by actors with some static meshes and boxes defining the ground and walls, an attach point to set where to attach the next tile, and a collision box that triggers the spawning. When a player runs through a tile and hits the collision box, a new tile is spawned at the attach point of the last tile.

Here is my current set up:

I have a base tile class in C++: ABaseTile that contains things that all tiles have, like the attach point and the collision box. What I would like to do is extend this base tile in blueprints to all my various tile permutations. Changing the obstacles on the tile and the various static meshes etc. Seems like a blue print would be the perfect place to do that. So I just create some blueprints based off of my ABaseTile and add static meshes and what not.

Now, When the player hits the collision box of a tile, I would like to randomly choose one of these blueprint tiles and spawn it at the end of my course. The spawning occurs in the GameMode object

My plan was to create a TArray TileList in the GameMode, create a blueprint from the game mode, and in the blueprint add my various tiles to that list. Then when the game is running, randomly select an index from that list and spawn the tile.

The problem I am running in to is that the SpawnActor function is looking for a Type for the template to spawn, and I believe using the UClass version is crashing my game…

Here is the relevant code:

GameMode.h

    UPROPERTY(EditAnywhere, Category = "Tile Types")
    	TArray< TSubclassOf<ABaseTile> > TileList;

GameMode.cpp

void AVelociRaptorCGameMode::AddTile()
{
	ABaseTile* newTile = Cast<ABaseTile>(GetWorld()->SpawnActor(TileList[0], &NextSpawnPoint));
	NextSpawnPoint = newTile->GetAttachPoint();
}

Here I am only trying to spawn copies of the first tile type TileList[0].
Does anyone know of a slick way to accomplish this?

Thanks!

void AVelociRaptorCGameMode::AddTile()
{
UClass* selectedClass = TileList[FMath::RandRange(0, TileList.Num()-1)];
ABaseTile* newTile = Cast(GetWorld()->SpawnActor(selectedClass, &NextSpawnPoint));
if (newTile)
{
NextSpawnPoint = newTile->GetAttachPoint();
}
}

Hi, Thank you for the reply. It looks like I was on the right track, however, my game (and the entire editor) is crashing when it hits this line:

ABaseTile* newTile = Cast<ABaseTile>(GetWorld()->SpawnActor(selectedClass, &NextSpawnPoint));

Am I setting this up properly? I have a TArray of TSubclassOf that is EditAnywhere in blueprints. I make a blueprint from my gamemode class. I add my derived tile blueprint to the array. Does that sound like the correct process?

Thanks!

EDIT: It does appear to work if instead of adding my blueprint derived class to the list, I add the ABaseTile class…

EDIT2: EDIT1 is wrong, it still didnt work. But see below.

What does the VS output window say? That is pretty important when it comes to crashes.

Part of me feels like it is crashing because I forgot a pointer, though if the compiler actually allowed you to compile this might be an implicit cast.

UClass* selectedClass = *(TileList[FMath::RandRange(0, TileList.Num()-1)]);

Another thing to try is the following:

 ABaseTile* newTile = GetWorld()->SpawnActor<ABaseTile>(selectedClass, &NextSpawnPoint);

Ok, I found the problem!

First: It is actually spawning new tiles. However, I forgot to check that the overlap of the collision box of the new tile is with the player, so as soon as it spawned, the collision box would overlap with the ground and throw a new collision which would spawn a new tile and the process would repeat… forever. Oops… So I fixed that:

AMyCharacter* player = Cast<AMyCharacter>(OtherActor);
	if (player)
	{
		//Spawn next tile
		...

	}

After that, I just had to make sure that the attach point was far enough forward that the newly spawned tile didnt immediately overlap with my player again, causing another loop.

And it worked! Thank you for your help!

Full Code for future reference:
GameMode.h:

//Array of Tile Types
	UPROPERTY(EditAnywhere, Category = "Tile Types")
	TArray< TSubclassOf<ABaseTile> > TileList;

GameMode.cpp

void AGameMode::AddTile()
{
	UClass* selectedClass = *TileList[0];
	ABaseTile* newTile = Cast<ABaseTile>(GetWorld()->SpawnActor(selectedClass, &NextSpawnPoint));
	
	if (newTile)
		NextSpawnPoint = newTile->GetAttachPoint();
}

BaseTile.cpp

void ABaseTile::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	AMyCharacter* player = Cast<AMyCharacter>(OtherActor);
	if (player)
	{
		// Destroy tile in 2 seconds
		FTimerHandle tempHandle;
		GetWorldTimerManager().SetTimer(tempHandle, this, &ABaseTile::DestroyTile, 2.0f, false);

		//Spawn next tile
		AGameMode* gamemode = GetWorld()->GetAuthGameMode<AGameMode>();
		if (gamemode)
			gamemode->AddTile();
	}
}