How do I spawn a random blueprint from a list at run time through blueprint?

What I want to do is select from a set of level tiles and create them at specified locations in the world, however I simply can’t find a way to actually make my blueprint create them. I can’t simply add them as instanced static meshes as they need their own custom behavior for things like doors, light switches and other things that might be found in each tile. Spawn actor from class seems to be the thing I want, but that seems to only spawn a very specific class and it doesn’t seem that there’s anyway to correctly connect an array of blueprints to Spawn actor from class. Running the get class on an array of blueprints returns an object, and it needs to be an actor. If I try casting to an actor from the object, it fails.

This seems like it should be a relatively easy thing to do, but I’m really at the end of my wits here, with no idea how I can really go about this.

I recommend reading through these links, especially the first:

Map Generator- Please Critique! - Blueprint - Unreal Engine Forums!

Map Generator 2.0 - Please Critique! - Blueprint - Unreal Engine Forums!

It depends on what you are doing, but one of the best ways to use instances is to pair them with invisible collision volumes that handle your interactions.

I read through both those links before posting the question, but either I missed something or it doesn’t really fit what I’m after. Essentially, what I want to be able to do it create a group of assets that will form a complex tile, e.g. a room in a house that is furnished and filled with various interactive/dynamic elements, and be able to select randomly from a list of these tiles and create the selected tile at a specified point in the world.

I can do this will a single tile (Spawn actor from class), but I don’t know how to create more than one tile that can be stored in an array that can also be compatible with some method of spawning. Object or blueprint type arrays seem to hold the tiles, but I can’t use spawn actor from class with them, as blueprint treats those as objects rather than actors.

What you probably want to do is create a blueprint for each room. This blueprint has “On Event Begin Play” and as soon as it either spawns into the world or is streamed in, it spawns it’s room using whatever predefined base transform. So you could spawn the blueprint actor at 1000x 1000y from a “Current Spawn Vector” variable, and then all your vectors for the room spawn is referenced to this location/variable. Your room would already know what order to spawn in, it just needs to reference the desired vector and fill in around it.

I basically do this with my map generator. I spawn it into the world and as soon as it hits the level it generates and spawns the map. Your extra step would be to feed in whatever vector the room is spawning around.

Okay, I found a solution. I think my problem was with understanding the difference between classes, objects, actors, and all that stuff. Essentially, what I did was what I think you were suggesting. I made a parent blueprint, that contains things like it’s index, position, and whether it’s connected to the rest of the level. I then used that class (class, not type, the purple one!) as the array’s type in the generator blueprint, then every tile I make will be a child of that class, which allows it to be selected and fed in. Then I can simply select one from the array and spawn it using spawn actor from class.

I could have sworn I already tried that last night, but thank you for your help. Although I didn’t really follow what you said, I think I got to where you were pointing in the end.

My solution…

We choose a blueprint class string found by the menu on the COntent Browser item, Copy Reference.

The copied string looks like…
Blueprint’/Game/Blueprints/Loot/Weapons/Ranged/Handguns/weapon_JmacPistol.weapon_JmacPistol’

Then we have C++ code to take that string and spawn…

AActor * UtdlBlueprintHelpers::SpawnActorFromReferenceString(AActor * sourceActor, FTransform trans, FString blueprintRefString, FName nameToAssign, bool CalculateLevelSubBlock)
{
	//UE_LOG(TDLLog, Log, TEXT("SpawnActorFromReferenceString"));
	if (sourceActor == NULL)
	{
		UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString MUST have an Actor."));
		return NULL;
	}

	if (blueprintRefString.IsEmpty())
	{
		UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString MUST have an Blueprint Ref String."));
		return NULL;
	}

	//UE_LOG(TDLLog, Log, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString %s"), *blueprintRefString);

	AActor * a = NULL;

	FStringAssetReference itemRef = blueprintRefString;

	if (itemRef.TryLoad() != NULL)
	{
		UObject* itemObj = itemRef.ResolveObject();
		if (itemObj == NULL)
		{
			UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString invalid blueprint reference (resolve falied): %s"), *itemRef.AssetLongPathname);
			return NULL;
		}
		UBlueprint* gen = Cast<UBlueprint>(itemObj);
		if (gen == NULL)
		{
			UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString invalid blueprint reference (cast failed): %s"), *itemRef.AssetLongPathname);
			return NULL;
		}

		FActorSpawnParameters SpawnInfo;
		SpawnInfo.bNoFail = true;
		SpawnInfo.bNoCollisionFail = true;
		SpawnInfo.bRemoteOwned = false;
		if (CalculateLevelSubBlock)
		{
			ULevel * foundLvl = CalcSubBlockFromWorldPosition(sourceActor, trans.GetLocation());
			if (foundLvl == NULL)
			{
				UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString Error. No matching block/"));
				return NULL;
			}
			SpawnInfo.OverrideLevel = foundLvl;
		}
		else
		{
			SpawnInfo.OverrideLevel = GetActorLevel(sourceActor);
		}
		if (nameToAssign.IsValid() && nameToAssign.ToString().Len() > 0)
		{
			SpawnInfo.Name = nameToAssign;
		}

		a = sourceActor->GetWorld()->SpawnActor<AActor>(gen->GeneratedClass, SpawnInfo);
		if (a == NULL)
		{
			UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::SpawnActorFromReferenceString invalid blueprint reference (spawn failed): %s"), *itemRef.AssetLongPathname);
			return NULL;
		}

		// Is it an NPC ?
		if (a->IsA(APawn::StaticClass()))
		{
			APawn * pwn = (APawn *)a;
			if (pwn->Controller == NULL)
			{	// NOTE: SpawnDefaultController ALSO calls Possess() to possess the pawn (if a controller is successfully spawned).
				pwn->SpawnDefaultController();
			}
		}

		a->SetActorTransform(trans);
	}

	return a;
}

But in a tiled world you need the CalcSubBlockFromWorldPosition which is…
** WARNING - This assumes you name your tiles like MyTile_x12_y3 **
If you don’t use tiles then the above code is a little simpler.

ULevel * UtdlBlueprintHelpers::CalcSubBlockFromWorldPosition(AActor * sourceActor, FVector wp)
{
	//UE_LOG(TDLLog, Log, TEXT("CalcSubBlockFromWorldPosition"));
	if (sourceActor == NULL)
	{
		UE_LOG(TDLLog, Error, TEXT("UtdlBlueprintHelpers::CalcSubBlockFromWorldPosition sourceActor is NULL."));
	}

	UWorld * w = sourceActor->GetWorld();
	if (w == NULL)
	{
		UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::CalcSubBlockFromWorldPosition sourceActor has no world?"));
		return NULL;
	}

	TArray<ULevel *> a = w->GetLevels();
	for (int i = 0; i < a.Num(); i++)
	{
		ULevel * v = a[i];
		int x = GetLevelTileX(v);
		if (x != -666)
		{
			int y = GetLevelTileY(v);
			if (y != -666)
			{
				//UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::CalcSubBlockFromWorldPosition level is: %s : %s   world: %s  [%d,%d]"),
				//	*(v->GetPathName()), *(v->GetName()), *(w->GetName()), x, y);
				
				// WARNING - Tiles are One-Based, not Zero-Based. And Tile 1,1 lower corner is at -8k, -8k
				// ANd tiles are 2014 x 20148.
				float lowX = (x - 1) * 204800.0f - 819200.0f;
				float hiX = x * 204800.0f - 819200.0f;
				float lowY = (y - 1) * 204800.0f - 819200.0f;
				float hiY = y * 204800.0f - 819200.0f;

				if (wp.X >= lowX && wp.X < hiX && 
					wp.Y >= lowY && wp.Y < hiY)
				{
					return v;
				}
			}
		}
	}

	//UE_LOG(TDLLog, Warning, TEXT("UtdlBlueprintHelpers::CalcSubBlockFromWorldPosition No matching sub-block? "));
	return NULL;
}

And you need the X and Y calc methods…

int32 UtdlBlueprintHelpers::GetActorLevelTileX(AActor * actor)
{
	//UE_LOG(TDLLog, Log, TEXT("GetActorLevelTileX"));
	if (actor == NULL)
	{
		return -666;
	}
	ULevel * lev = actor->GetLevel();
	return GetLevelTileX(lev);
}

int32 UtdlBlueprintHelpers::GetActorLevelTileY(AActor * actor)
{
	//UE_LOG(TDLLog, Log, TEXT("GetActorLevelTileY"));
	if (actor == NULL)
	{
		return -666;
	}
	ULevel * lev = actor->GetLevel();
	return GetLevelTileY(lev);
}

int32 UtdlBlueprintHelpers::GetLevelTileX(ULevel * lev)
{
	//UE_LOG(TDLLog, Log, TEXT("GetLevelTileX"));
	if (lev == NULL)
	{
		return -666;
	}

	UObject * world = lev->GetOuter();
	if (world == NULL)
	{
		return -666;
	}

	int32 x = 0;
	FString nm = world->GetName();
	//             0         1
	//             012345678901234567
	// nm is like "PepperValley_x4_y3"
	int32 ix = nm.Find(FString("_x"));
	int32 iy = nm.Find(FString("_y"));
	int32 xsz = iy - ix - 2;
	int32 ysz = nm.Len() + 1 - iy - 2;
	if (ix >= 0 && iy >= 0 && xsz > 0 && ysz > 0)
	{
		char * s = TCHAR_TO_ANSI(*(nm.Mid(ix + 2, xsz)));
		int n = sscanf_s(s, "%d", &x);
		if (n != 1)
		{
			return -666;
		}
		return x;
	}
	return -666;
}


int32 UtdlBlueprintHelpers::GetLevelTileY(ULevel * lev)
{
	//UE_LOG(TDLLog, Log, TEXT("GetLevelTileY"));
	if (lev == NULL)
	{
		return -666;
	}

	UObject * world = lev->GetOuter();
	if (world == NULL)
	{
		return -666;
	}

	int32 y = 0;
	FString nm = world->GetName();
	//             0         1
	//             012345678901234567
	// nm is like "PepperValley_x4_y3"
	int32 ix = nm.Find(FString("_x"));
	int32 iy = nm.Find(FString("_y"));
	int32 xsz = iy - ix - 2;
	int32 ysz = nm.Len() + 1 - iy - 2;
	if (ix >= 0 && iy >= 0 && xsz > 0 && ysz > 0)
	{
		char * s = TCHAR_TO_ANSI(*(nm.Mid(iy + 2, ysz)));
		int n = sscanf_s(s, "%d", &y);
		if (n != 1)
		{
			return -666;
		}
		return y;
	}
	return -666;
}

Note: The sub tile code is for large worlds that are tiled. We use a tiled world 20km sq. with 2km tiles. We want to have actors owend by the tile, not the top level global world.

Then when we are saving world state wo do it on a file-per-tile basis.

See World Composition in Unreal Engine | Unreal Engine 5.1 Documentation