PocketWorld是UE5 Lyra Demo中的插件,用于在UI中预览3D内容,其内部包含了RT生成等逻辑。这部分主要分享PocketLevel的使用,通过流送形式加载关卡进行拍摄。以及一些PocketWorld代码相关内容。
上半部分:https://blog.csdn.net/grayrail/article/details/163824157
最终效果:

1.将Init函数更改为事件

2.初始化部分沿用上半部分文章的逻辑,图像绑定更换为SetBrushResourceObject接口,便于演示。

3.因为PocketLevel部分没有被标记为蓝图可调,需要修改C++。
PocketLevelSystem.h
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Subsystems/WorldSubsystem.h"
#include "PocketLevelSystem.generated.h"
class ULocalPlayer;
class UObject;
class UPocketLevel;
class UPocketLevelInstance;
/**
* Manages streaming pocket level instances per local player.
*/
UCLASS(BlueprintType)
class POCKETWORLDS_API UPocketLevelSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Pocket Level", meta = (WorldContext = "WorldContextObject"))
static UPocketLevelSubsystem* Get(const UObject* WorldContextObject);
/** Loads or reuses a pocket level instance for the given player. */
UFUNCTION(BlueprintCallable, Category = "Pocket Level")
UPocketLevelInstance* GetOrCreatePocketLevelFor(ULocalPlayer* LocalPlayer, UPocketLevel* PocketLevel, FVector DesiredSpawnPoint);
private:
UPROPERTY()
TArray<TObjectPtr<UPocketLevelInstance>> PocketInstances;
};
PocketLevelSystem.cpp
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PocketLevelSystem.h"
#include "Engine/Engine.h"
#include "PocketLevel.h"
#include "PocketLevelInstance.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PocketLevelSystem)
UPocketLevelSubsystem* UPocketLevelSubsystem::Get(const UObject* WorldContextObject)
{
if (const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
{
return World->GetSubsystem<UPocketLevelSubsystem>();
}
return nullptr;
}
UPocketLevelInstance* UPocketLevelSubsystem::GetOrCreatePocketLevelFor(ULocalPlayer* LocalPlayer, UPocketLevel* PocketLevel, FVector DesiredSpawnPoint)
{
if (PocketLevel == nullptr)
{
return nullptr;
}
float VerticalBoundsOffset = 0;
for (UPocketLevelInstance* Instance : PocketInstances)
{
if (Instance->LocalPlayer == LocalPlayer && Instance->PocketLevel == PocketLevel)
{
return Instance;
}
VerticalBoundsOffset += Instance->PocketLevel->Bounds.Z;
}
const FVector SpawnPoint = DesiredSpawnPoint + FVector(0, 0, VerticalBoundsOffset);
UPocketLevelInstance* NewInstance = NewObject<UPocketLevelInstance>(this);
NewInstance->Initialize(LocalPlayer, PocketLevel, SpawnPoint);
PocketInstances.Add(NewInstance);
return NewInstance;
}
PocketLevelInstance.h
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Math/BoxSphereBounds.h"
#include "UObject/ObjectPtr.h"
#include "PocketLevelInstance.generated.h"
class UPocketLevelSubsystem;
class ULevelStreamingDynamic;
class AActor;
class ULocalPlayer;
class UPocketLevel;
class UPocketLevelInstance;
class UWorld;
struct FFrame;
DECLARE_MULTICAST_DELEGATE_OneParam(FPocketLevelInstanceEvent, UPocketLevelInstance*);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPocketLevelInstanceReady, UPocketLevelInstance*, PocketLevelInstance);
/**
* A single streamed pocket level instance owned by PocketLevelSubsystem.
*/
UCLASS(Within = PocketLevelSubsystem, BlueprintType)
class POCKETWORLDS_API UPocketLevelInstance : public UObject
{
GENERATED_BODY()
public:
UPocketLevelInstance();
virtual void BeginDestroy() override;
UFUNCTION(BlueprintCallable, Category = "Pocket Level")
void StreamIn();
UFUNCTION(BlueprintCallable, Category = "Pocket Level")
void StreamOut();
UFUNCTION(BlueprintCallable, Category = "Pocket Level")
bool IsReady() const;
UFUNCTION(BlueprintCallable, Category = "Pocket Level")
FVector GetSpawnOrigin() const;
UFUNCTION(BlueprintCallable, Category = "Pocket Level")
TArray<AActor*> GetLevelActors() const;
FDelegateHandle AddReadyCallback(FPocketLevelInstanceEvent::FDelegate Callback);
void RemoveReadyCallback(FDelegateHandle CallbackToRemove);
UPROPERTY(BlueprintAssignable, Category = "Pocket Level")
FOnPocketLevelInstanceReady OnReady;
virtual class UWorld* GetWorld() const override { return World; }
private:
bool Initialize(ULocalPlayer* LocalPlayer, UPocketLevel* PocketLevel, FVector SpawnPoint);
UFUNCTION()
void HandlePocketLevelLoaded();
UFUNCTION()
void HandlePocketLevelShown();
private:
UPROPERTY()
TObjectPtr<ULocalPlayer> LocalPlayer;
UPROPERTY()
TObjectPtr<UPocketLevel> PocketLevel;
UPROPERTY()
TObjectPtr<UWorld> World;
UPROPERTY()
TObjectPtr<ULevelStreamingDynamic> StreamingPocketLevel;
FPocketLevelInstanceEvent OnReadyEvent;
FBoxSphereBounds Bounds;
friend class UPocketLevelSubsystem;
};
PocketLevelInstance.cpp
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PocketLevelInstance.h"
#include "Engine/Level.h"
#include "Engine/LevelStreaming.h"
#include "Engine/LevelStreamingDynamic.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "PocketLevel.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PocketLevelInstance)
UPocketLevelInstance::UPocketLevelInstance()
{
}
bool UPocketLevelInstance::Initialize(ULocalPlayer* InLocalPlayer, UPocketLevel* InPocketLevel, FVector InSpawnPoint)
{
LocalPlayer = InLocalPlayer;
World = LocalPlayer->GetWorld();
PocketLevel = InPocketLevel;
Bounds = FBoxSphereBounds(FSphere(InSpawnPoint, PocketLevel->Bounds.GetAbsMax()));
if (ensure(StreamingPocketLevel == nullptr))
{
if (ensure(!PocketLevel->Level.IsNull()))
{
bool bSuccess = false;
StreamingPocketLevel = ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr(LocalPlayer, PocketLevel->Level, Bounds.Origin, FRotator::ZeroRotator, bSuccess);
if (ensure(bSuccess && StreamingPocketLevel))
{
StreamingPocketLevel->OnLevelLoaded.AddUniqueDynamic(this, &ThisClass::HandlePocketLevelLoaded);
StreamingPocketLevel->OnLevelShown.AddUniqueDynamic(this, &ThisClass::HandlePocketLevelShown);
}
return bSuccess;
}
}
return false;
}
void UPocketLevelInstance::StreamIn()
{
if (StreamingPocketLevel)
{
StreamingPocketLevel->SetShouldBeVisible(true);
StreamingPocketLevel->SetShouldBeLoaded(true);
}
}
void UPocketLevelInstance::StreamOut()
{
if (StreamingPocketLevel)
{
StreamingPocketLevel->SetShouldBeVisible(false);
StreamingPocketLevel->SetShouldBeLoaded(false);
}
}
bool UPocketLevelInstance::IsReady() const
{
return StreamingPocketLevel && StreamingPocketLevel->GetLevelStreamingState() == ELevelStreamingState::LoadedVisible;
}
FVector UPocketLevelInstance::GetSpawnOrigin() const
{
return Bounds.Origin;
}
TArray<AActor*> UPocketLevelInstance::GetLevelActors() const
{
TArray<AActor*> Result;
if (StreamingPocketLevel)
{
if (const ULevel* LoadedLevel = StreamingPocketLevel->GetLoadedLevel())
{
for (AActor* Actor : LoadedLevel->Actors)
{
if (Actor)
{
Result.Add(Actor);
}
}
}
}
return Result;
}
FDelegateHandle UPocketLevelInstance::AddReadyCallback(FPocketLevelInstanceEvent::FDelegate Callback)
{
if (StreamingPocketLevel && StreamingPocketLevel->GetLevelStreamingState() == ELevelStreamingState::LoadedVisible)
{
Callback.ExecuteIfBound(this);
}
return OnReadyEvent.Add(Callback);
}
void UPocketLevelInstance::RemoveReadyCallback(FDelegateHandle CallbackToRemove)
{
OnReadyEvent.Remove(CallbackToRemove);
}
void UPocketLevelInstance::BeginDestroy()
{
Super::BeginDestroy();
if (StreamingPocketLevel)
{
StreamingPocketLevel->bShouldBlockOnUnload = false;
StreamingPocketLevel->SetShouldBeLoaded(false);
StreamingPocketLevel->OnLevelShown.RemoveAll(this);
StreamingPocketLevel->OnLevelLoaded.RemoveAll(this);
StreamingPocketLevel = nullptr;
}
}
void UPocketLevelInstance::HandlePocketLevelLoaded()
{
if (StreamingPocketLevel)
{
// Make everything in the level setup so that it's setup on the client, and we treat
// everything as locally spawned, rather than bExchangedRoles = true, where it's spawned
// on the client, but the expectation is the server said do it, and the server is going to
// be telling us about them later.
if (ULevel* LoadedLevel = StreamingPocketLevel->GetLoadedLevel())
{
LoadedLevel->bClientOnlyVisible = true;
for (AActor* Actor : LoadedLevel->Actors)
{
if (Actor)
{
Actor->bExchangedRoles = true; // HACK, Remove when bClientOnlyVisible is all we need.
}
}
// TODO: Don't put ownership over shared pocket spaces.
if (LocalPlayer)
{
if (APlayerController* PC = LocalPlayer->GetPlayerController(GetWorld()))
{
for (AActor* Actor : LoadedLevel->Actors)
{
if (Actor)
{
Actor->SetOwner(PC);
}
}
}
}
}
}
}
void UPocketLevelInstance::HandlePocketLevelShown()
{
OnReadyEvent.Broadcast(this);
OnReady.Broadcast(this);
}
PocketLevel.h
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/DataAsset.h"
#include "PocketLevel.generated.h"
class UObject;
class UWorld;
/**
* Data asset describing a pocket level to stream in off-world.
*/
UCLASS(BlueprintType)
class POCKETWORLDS_API UPocketLevel : public UDataAsset
{
GENERATED_BODY()
public:
UPocketLevel();
public:
// The level that will be streamed in for this pocket level.
UPROPERTY(EditAnywhere, Category = "Streaming")
TSoftObjectPtr<UWorld> Level;
// The bounds of the pocket level so that we can create multiple instances without overlapping each other.
UPROPERTY(EditAnywhere, Category = "Streaming")
FVector Bounds;
};
4.Pocket Level部分蓝图接口,其中Get or Create Pocket Level For中的参数暂时没有先不填。

5.创建空场景CaptureMap

6.创建DataAsset对象PocketLevel01,配置Level,设置Level的Bounds,该参数会被应用于堆叠逻辑。

7.补全Get or Create Pocket Level For的参数。运行测试即可。