Loading Maps from C++, Working Around GWorld::EWorldType

I have a module in my project that, from C++, will allow individual levels to be enabled or disabled as the game starts up. To load levels from C++, I’m using the UGameplayStatics::LoadStreamLevel function. It seems this function only works if I give it a complete path to the level’s .umap file (as opposed to blueprint, where the LoadStreamLevel node appears to work with nothing more than a level name).

To supply it with one, I’ve made this helper function:

FString LevelStreamMan::MakeLevelPathFromSpaceName(const FString& SpaceName)
{
	return FString("/Game/" + ShipName + "/" + SpaceName + "/Maps/" +
		GWorld->StreamingLevelsPrefix + SpaceName);
}

This thing has two purposes: first it makes it so I don’t have to specify the full path to every map I want to load when I start the game; and second, it uses the GWorld’s StreamingLevelsPrefix variable to point to the correct level package depending on the way I launched the game.

If I ask it for the path to the space “Passage_13”, if I’m running in PIE, the function will return “/Game/Leviathan/Passage_13/Maps/UEDPIE_0_Passage_13”. When I convert this to an FName and pass it to LoadStreamLevel, the level loads successfully. If I run in standalone from the editor, it will return “/Game/Leviathan/Passage_13/Maps/PCPassage_13”.

The problem, though, is that the latter path only “sometimes” works when I hand it off to LoadStreamLevel. The path to the levels appears to change depending on whether I launched from the editor or from something else, such as a custom launcher app or a debugger, with the -game flag set.

Just to test this I added a block to the function:

// Editor standalone only: Load levels from the Autosaves directory
if (GWorld->WorldType == EWorldType::Game)
{
	ret = "/Temp/Autosaves/";
}

// ...append the rest of the string here

This works when I launch standalone from the editor because it always attempts to load autosaved maps, but it doesn’t work this way when I launch from the debugger. If I remove this block, then it works in the debugger, but not from the editor in standalone.

I’d like to have something that works in both scenarios. Is there something I’m missing here as far as telling the difference between these two so I can make the function adapt accordingly? Or am I completely on the wrong track and the solution is far simpler than I think? I’m also a bit curious as to how Blueprint is able to simply use level names without supplying full paths or prefixes when it calls the LoadStreamLevel node.

Hi,

You should look at FStreamLevelAction::FindAndCacheLevelStreamingObject function.

This function finds a level streaming object by a name received from blueprint node. It handles PIE/Game and short/long name cases in FStreamLevelAction::MakeSafeLevelName.

Works for both PIE and Standalone, also it allows to load level which is not listed inside “Levels” window (fully dynamic). Also, possible to load same level multiple times.

Based on Loading Sub-Level multiple times, possible? - C++ Programming - Unreal Engine Forums

bool UMyBlueprintFunctionLibrary::LoadLevel(FName LevelToStream, AActor* Actor, FTransform Transform) {
	if (!Actor) 
	{
		UE_LOG(LogTemp, Error, TEXT("Actor is empty"));
		return false;
	}
	UWorld* World = Actor->GetWorld();
	if (!World)
	{
		UE_LOG(LogTemp, Error, TEXT("World taken from Actor is empty"));
		return false;
	}

	FString PackageFileName = LevelToStream.ToString();		
	ULevelStreamingKismet* StreamingLevel = NewObject<ULevelStreamingKismet>((UObject*)GetTransientPackage(), ULevelStreamingKismet::StaticClass());

	// Associate a package name.
	StreamingLevel->SetWorldAssetByPackageName(LevelToStream);
	if (World->IsPlayInEditor())
	{
		FWorldContext WorldContext = GEngine->GetWorldContextFromWorldChecked(World);
		StreamingLevel->RenameForPIE(WorldContext.PIEInstance);
	}

	StreamingLevel->LevelColor = FColor::MakeRandomColor();
	StreamingLevel->bShouldBeLoaded = true;
	StreamingLevel->bShouldBeVisible = true;
	StreamingLevel->bShouldBlockOnLoad = false;
	StreamingLevel->bInitiallyLoaded = true;
	StreamingLevel->bInitiallyVisible = true;

	StreamingLevel->LevelTransform = Transform;

	StreamingLevel->PackageNameToLoad = LevelToStream;
		
	if (!FPackageName::DoesPackageExist(StreamingLevel->PackageNameToLoad.ToString(), NULL, &PackageFileName))
	{		
		UE_LOG(LogTemp, Error, TEXT("Package does not exists"));
		return false;
	}

	StreamingLevel->PackageNameToLoad = FName(*FPackageName::FilenameToLongPackageName(PackageFileName));

	// Add the new level to world.
	World->StreamingLevels.Add(StreamingLevel);
	

	return true;
}

#BP Node Created For Community Use

I’ve created a BP node for the community to use which has a special solution for ensuring unique instance names to guarantee that multiple instances of level can be spawned.

Special thanks to nkey for posting the forum link!

Victory BP Library Thread

My entire modified C++ code at link above

Enjoy!

Rama