Thank you @MSTF. That is very useful. This works during runtime, however the editor gives errors since we change the package names as a workaround in that thread. So I’ve made two implementations, one for the editor and another for the runtime. If it is a runtime only build (e.g. standalone game) it would use the implementation mentioned in the thread. If we are in the editor, I use the editor services to clone the level files and map them to the world composition so we don’t get any warnings in the editor
This way it solves the problem in both editor and non-editor builds
's the code, if anyone is interested on getting something like this done
Non-Editor Builds (e.g. standalone)
ULevel* FDungeonNonEditorFallbackService::CreateStreamingLevelInstance(UWorld* InWorld, const FTransform& Transform, const TAssetPtr<UWorld>& WorldAsset, UClass* LevelStreamingClass) {
ULevel* NewLevel = NULL;
if (LevelStreamingClass == NULL) {
return NULL;
}
ULevelStreaming* StreamingLevel = NewObject<ULevelStreaming>(InWorld, LevelStreamingClass, NAME_None, RF_NoFlags, NULL);
FString PackageName = WorldAsset.GetLongPackageName();
FString UniquePackageName = PackageName + "_" + FGuid::NewGuid().ToString();
StreamingLevel->SetWorldAssetByPackageName(FName(*UniquePackageName));
StreamingLevel->LevelTransform = Transform;
StreamingLevel->PackageNameToLoad = FName(*PackageName);
StreamingLevel->bShouldBeLoaded = true;
StreamingLevel->bShouldBeVisible = true;
StreamingLevel->bShouldBlockOnLoad = false;
StreamingLevel->LevelColor = FLinearColor::MakeRandomColor();
// Add the new level to world.
InWorld->StreamingLevels.Add(StreamingLevel);
// Refresh just the newly created level.
TArray<ULevelStreaming*> LevelsForRefresh;
LevelsForRefresh.Add(StreamingLevel);
InWorld->RefreshStreamingLevels(LevelsForRefresh);
InWorld->MarkPackageDirty();
NewLevel = StreamingLevel->GetLoadedLevel();
if (NewLevel != nullptr)
{
//EditorLevelUtils::SetLevelVisibility(NewLevel, true, true);
// Levels migrated from other projects may fail to load their world settings
// If so we create a new AWorldSettings actor .
if (NewLevel->GetWorldSettings(false) == nullptr)
{
UWorld* SubLevelWorld = CastChecked<UWorld>(NewLevel->GetOuter());
FActorSpawnParameters SpawnInfo;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnInfo.Name = GEngine->WorldSettingsClass->GetFName();
AWorldSettings* NewWorldSettings = SubLevelWorld->SpawnActor<AWorldSettings>(GEngine->WorldSettingsClass, SpawnInfo);
NewLevel->SetWorldSettings(NewWorldSettings);
}
}
return NewLevel;
}
Editor Builds
template<typename T>
static T* CloneAsset(const FString& TemplatePath, const FString& TargetName, const FString& TargetDirectory) {
T* Template = LoadObject<T>(NULL, *TemplatePath, NULL, LOAD_None, NULL);
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
UObject* AssetObject = AssetTools.DuplicateAsset(TargetName, TargetDirectory, Template);
T* Asset = Cast<T>(AssetObject);
if (!Asset) {
UE_LOG(LogEditorService, Warning, TEXT("Failed to clone asset at location %s/%s"), *TargetDirectory, *TargetName);
}
return Asset;
}
ULevel* FDungeonEditorService::CreateStreamingLevelInstance(UWorld* InWorld, const FTransform& Transform, const TAssetPtr<UWorld>& WorldAsset, UClass* LevelStreamingClass)
{
ULevel* NewLevel = nullptr;
if (LevelStreamingClass == NULL) {
return NULL;
}
ULevelStreaming* StreamingLevel = NewObject<ULevelStreaming>(InWorld, LevelStreamingClass, NAME_None, RF_NoFlags, NULL);
// Clone the streaming world
if (!InWorld || !InWorld->GetOutermost()) {
UE_LOG(LogEditorService, Error, TEXT("Cannot create streaming level. World context state is invalid"));
return nullptr;
}
FString ClonedAssetName = WorldAsset->GetName() + "_" + FGuid::NewGuid().ToString();
FString ClonedAssetPath = InWorld->GetOutermost()->GetName() + "_SnapStreamingInstances";
UWorld* ClonedWorld = CloneAsset<UWorld>(WorldAsset.GetLongPackageName(), ClonedAssetName, ClonedAssetPath);
if (!ClonedWorld) {
UE_LOG(LogEditorService, Error, TEXT("Failed to clone snap module"));
return nullptr;
}
FStringAssetReference ClonedWorldAssetRef(ClonedWorld);
FString ClonedWorldPath = ClonedWorldAssetRef.ToString();
StreamingLevel->SetWorldAssetByPackageName(FName(*ClonedWorldPath));
StreamingLevel->LevelTransform = Transform;
StreamingLevel->bShouldBeLoaded = true;
StreamingLevel->bShouldBeVisible = true;
StreamingLevel->bShouldBlockOnLoad = false;
StreamingLevel->LevelColor = FLinearColor::MakeRandomColor();
// Add the new level to world.
InWorld->StreamingLevels.Add(StreamingLevel);
// Refresh just the newly created level.
TArray<ULevelStreaming*> LevelsForRefresh;
LevelsForRefresh.Add(StreamingLevel);
InWorld->RefreshStreamingLevels(LevelsForRefresh);
InWorld->MarkPackageDirty();
NewLevel = StreamingLevel->GetLoadedLevel();
if (NewLevel != nullptr)
{
//EditorLevelUtils::SetLevelVisibility(NewLevel, true, true);
// Levels migrated from other projects may fail to load their world settings
// If so we create a new AWorldSettings actor .
if (NewLevel->GetWorldSettings(false) == nullptr)
{
UWorld* SubLevelWorld = CastChecked<UWorld>(NewLevel->GetOuter());
FActorSpawnParameters SpawnInfo;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnInfo.Name = GEngine->WorldSettingsClass->GetFName();
AWorldSettings* NewWorldSettings = SubLevelWorld->SpawnActor<AWorldSettings>(GEngine->WorldSettingsClass, SpawnInfo);
NewLevel->SetWorldSettings(NewWorldSettings);
}
}
return NewLevel;
}