Taking it a step further I created a couple of AsyncActions of these so they can be global BP Nodes. Hope this helps!
This works in PIE and Standalone Game run from Editor.
LoadStreamLevelAsyncAction.h
#pragma once
#include "Kismet/BlueprintAsyncActionBase.h"
#include "LoadStreamLevelAsyncAction.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLoadStreamLevel_OutputPin, ULevelStreaming*, level);
UCLASS()
class YOURGAME_API ULoadStreamLevelAsyncAction : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
UPROPERTY()
ULevelStreaming* Level;
UPROPERTY()
FTransform Transform;
public:
virtual void Activate() override;
UPROPERTY(BlueprintAssignable)
FOnLoadStreamLevel_OutputPin OnCompleted;
private:
UFUNCTION()
void LatentActionCallback();
public:
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "worldContextObject", AllowedClasses = "World", DisplayName = "Load Stream Level"), Category = "Trifecta|Level|Stream")
static ULoadStreamLevelAsyncAction* LoadStreamLevel(UObject* worldContextObject, const TSoftObjectPtr<UWorld>& _level, const FTransform& _transform);
};
LoadStreamLevelAsyncAction.cpp
#include "Level/LoadStreamLevelAsyncAction.h"
#include "LevelUtils.h"
#include "Engine/LevelStreaming.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/PackageName.h"
void ULoadStreamLevelAsyncAction::Activate()
{
if (Level && !Level->IsLevelLoaded())
{
FLatentActionInfo latentActionInfo;
latentActionInfo.CallbackTarget = this;
latentActionInfo.ExecutionFunction = FName("LatentActionCallback");
latentActionInfo.UUID = 0;
latentActionInfo.Linkage = 0;
UGameplayStatics::LoadStreamLevel(this, Level->PackageNameToLoad, false, false, latentActionInfo);
}
else if (Level == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("ERROR: Could not load level %s, level not found!"), *Level->PackageNameToLoad.ToString());
}
}
void ULoadStreamLevelAsyncAction::LatentActionCallback()
{
if (Level)
{
#if WITH_EDITOR
if (GIsEditor) // Also don't run when Standalone Game from the editor
FLevelUtils::SetEditorTransform(Level, Transform);
#endif
Level->LevelTransform = Transform;
Level->SetShouldBeVisible(true);
OnCompleted.Broadcast(Level);
}
}
ULoadStreamLevelAsyncAction* ULoadStreamLevelAsyncAction::LoadStreamLevel(UObject* worldContextObject, const TSoftObjectPtr<UWorld>& level, const FTransform& transform)
{
ULoadStreamLevelAsyncAction* asyncAction = NewObject<ULoadStreamLevelAsyncAction>(worldContextObject);
asyncAction->Level = UGameplayStatics::GetStreamingLevel(worldContextObject->GetWorld(), FName(*FPackageName::ObjectPathToPackageName(level.ToString())));
asyncAction->Transform = transform;
return asyncAction;
}
UnloadStreamLevelAsyncAction.h
#pragma once
#include "Kismet/BlueprintAsyncActionBase.h"
#include "UnloadStreamLevelAsyncAction.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUnloadStreamLevel_OutputPin, ULevelStreaming*, level);
UCLASS()
class YOURGAME_API UUnloadStreamLevelAsyncAction : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
UPROPERTY()
ULevelStreaming* Level;
public:
virtual void Activate() override;
UPROPERTY(BlueprintAssignable)
FOnUnloadStreamLevel_OutputPin OnCompleted;
private:
UFUNCTION()
void LatentActionCallback();
public:
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "worldContextObject", AllowedClasses = "World", DisplayName = "Unload Stream Level"), Category = "Trifecta|Level|Stream")
static UUnloadStreamLevelAsyncAction* UnloadStreamLevel(UObject* worldContextObject, const TSoftObjectPtr<UWorld>& _level);
};
UnloadStreamLevelAsyncAction.cpp
#include "Level/UnloadStreamLevelAsyncAction.h"
#include "LevelUtils.h"
#include "Engine/LevelStreaming.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/PackageName.h"
void UUnloadStreamLevelAsyncAction::Activate()
{
if (Level && !Level->IsLevelLoaded())
{
FLatentActionInfo latentActionInfo;
latentActionInfo.CallbackTarget = this;
latentActionInfo.ExecutionFunction = FName("LatentActionCallback");
latentActionInfo.UUID = 0;
latentActionInfo.Linkage = 0;
UGameplayStatics::UnloadStreamLevel(this, Level->PackageNameToLoad, latentActionInfo, false);
}
else if (Level == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("ERROR: Could not load level %s, level not found!"), *Level->PackageNameToLoad.ToString());
}
}
void UUnloadStreamLevelAsyncAction::LatentActionCallback()
{
#if WITH_EDITOR
if (Level && !Level->IsLevelLoaded())
{
// To avoid warnings in the editor, should be moved to Identity at this point.
FLevelUtils::SetEditorTransform(Level, FTransform());
}
#endif
OnCompleted.Broadcast(Level);
}
UUnloadStreamLevelAsyncAction* UUnloadStreamLevelAsyncAction::UnloadStreamLevel(UObject* worldContextObject, const TSoftObjectPtr<UWorld>& level)
{
UUnloadStreamLevelAsyncAction* asyncAction = NewObject<UUnloadStreamLevelAsyncAction>(worldContextObject);
asyncAction->Level = UGameplayStatics::GetStreamingLevel(worldContextObject->GetWorld(), FName(*FPackageName::ObjectPathToPackageName(level.ToString())));
return asyncAction;
}