UE4 C++ TSoftClassPtr(TObjectPtr)のメモ

やあ

メモなので期待しないでください。
一部、感想や想像が含まれます

FStreamableManagerの宣言場所

・SingletonやGameInstanceなど、値を永続的に保持する場所で宣言する。
レベル遷移中のアセットの読み込みとかそのキャンセルをするときに、そういった場所にあったほうがバグらなさそう。

Singleton
historia.co.jp

GameInstance
meganeo.blog.shinobi.jp

GameInstanceで宣言してみる

HogeGameInstance.h

#include "Engine/StreamableManager.h"

UCLASS()
class HOGEPROJECT_API UHogeGameInstance : public UGameInstance
{
    GENERATED_BODY()
public:
    static UHogeGameInstance* GetGameInstance();
    
    static FStreamableManager StreamableManager;
    static FStreamableManager& GetStreamableManger();
};

HogeGameInstance.cpp

#include "Engine.h"

FStreamableManager UHogeGameInstance::StreamableManager;

UHogeGameInstance UHogeGameInstance::GetGameInstance()
{
    UHogeGameInstance* instance = nullptr;
    if (GEngine != nullptr)
    {
        FWorldContext* context = GEngine->GetWorldContextFromGameViewport(GEngine->GameViewport);
        instance = Cast<UHogeGameInstance>(context->OwningGameInstance);
    }

    return instance;
}

FStreamableManager& UHogeGameInstance::GetStreamableManger()
{
    return StreamableManager;
}

これでGameInstanceからStreamableManagerを受け取って読み込みの処理をする。
AssetManagerを使ってるならそこで宣言したほうがまとまってていい気がする。
というか普通、ソフト参照にしたいUIのクラスとかもAssetManagerに突っ込むんですかね?

読み込み

TSoftClassPtrTSoftObjectPtrも同じ方法で読み込みができる。

同期読み込み(Synchronous)

hoge.h

class UUserWidget;

UCLASS()
class HOGEPROJECT_API AHoge : public ACharacter
{
    GENERATED_BODY();
public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
        TSoftClassPtr<UUserWidget> InventoryUIClass;
    UPROPERTY(BlueprintReadOnly)
        UUserWidget* InvnetoryUI;
    UFUNCTION(BlueprintCallable)
        void OpenInventoryUI();
};
void AHoge::OpenInventoryUI()
{
    UClass* _InventoryUIClass = InventoryUIClass.LoadSynchronous();
    InventoryUI = CreateWidget<UUserWidget>(GetWorld(), _InventoryUIClass);
    InventoryUI->AddToViewport();
}

もちろん同期読み込みのため、InventoryUIClassの読み込みが終わるまで処理が止まります。
なので、たくさんのソフト参照や、サイズの大きいものを同期読み込みしようとすると、
プレイヤーは、このゲーム重いわーとか思ったり思わなかったり。
それを防ぐための非同期読み込み。

非同期読み込み(Asynchronous)

hoge.h

class UUserWidget;

UCLASS()
class HOGEPROJECT_API AHoge : public ACharacter
{
    GENERATED_BODY();
public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
        TSoftClassPtr<UUserWidget> InventoryUIClass;
    UPROPERTY(BlueprintReadOnly)
        UUserWidget* InvnetoryUI;
    UFUNCTION(BlueprintCallable)
        void BeginLoadInventoryUI();
    UFUNCTION(BlueprintCallable)
        void FinishedLoadInventoryUI();
    TSharedPtr<FStreamableHandle> InventoryUIHandle;
};
#include "HogeGameInstance.h"

void AHoge::BeginLoadInventoryUI()
{
    FStreamableManager& StreamableManager = UHogeGameInstance::GetStreamableManager();
    const FSoftObjectPath ObjectPath = InventoryUIClass.ToSoftObjectPath();
    InventoryUIHandle = StreamableManager.RequestAsyncLoad(
        ObjectPath,
        FStreamableDelegate::CreateUObject(
            this, &AHoge::FinishedLoadInventoryUI
        );
    );
}

void AHoge::BeginLoadInventoryUI()
{
    if (InventoryUIHandle.IsValid())
    {
        UObject* LoadedObj = InventoryUIHandle.Get()->GetLoadedAsset();
        if (LoadedObj)
        {
            UClass* _InventoryUIClass = Cast<UClass>(LoadedObj);
            if (_InventoryUIClass)
            {
                InventoryUI = CreateWidget<UUserWidget>(GetWorld(), _InventoryUIClass);
                InventoryUI->AddToViewport();                
            }
        }
    }
}

もちろんデリゲートを使わずに一つの関数のなかでもできます。

デリゲートを使わない例

FStreamableManager& StreamableManager = UHogeGameInstance::GetStreamableManager();
const FSoftObjectPath ObjectPath = InventoryUIClass.ToSoftObjectPath();
InventoryUIHandle = StreamableManager.RequestAsyncLoad(
    ObjectPath,
    FStreamableDelegate::CreateUObject(
        this, &AHoge::FinishedLoadInventoryUI
    );
);
if (InventoryUIHandle.IsValid())
{
    UObject* LoadedObj = InventoryUIHandle.Get()->GetLoadedAsset();
    if (LoadedObj)
    {
        UClass* _InventoryUIClass = Cast<UClass>(LoadedObj);
        if (_InventoryUIClass)
        {
            InventoryUI = CreateWidget<UUserWidget>(GetWorld(), _InventoryUIClass);
            InventoryUI->AddToViewport();                
        }
    }
}

なお、ソフト参照のサイズが大きいと読み込みが間に合わずにLoadedObjnullptrになるぞ!

非同期読み込みのタイミングよくわからない。。。誰か教えてください。