くーこのプログラマメモ

UE4などゲーム開発に関するメモ

C++から変更アセットを保存する方法

使用バージョン:UE4.24.2

はじめに

エディタ上でアセットに対して何かしらの編集処理を行った際に自動で保存を実行したい場合があります。
そういった場合に対象アセットを保存するためのラッパー関数を用意する機会がありましたので、その備忘録となります。

準備

エディタ用のコードとなるため、エディタ用モジュールを用意しそちらへ実装します。

実装方法

依存モジュール

以下のモジュールを依存関係として追加する必要があります。

  • UnrealEd

ヘッダー

もし、BPから呼び出したい場合はUFUNCTIONでBPへ公開するよう設定すれば動作するかと思います。

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"

#include "AssetUtilityFunctionLibrary.generated.h"


UCLASS()
class XXX_API UAssetUtilityFunctionLibrary: public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
public:
    /**
    * アセットの保存を実行します
    *
    * @param [in]  InTargets           対象アセット
    * @param [in]  bCheckDirty         trueの場合は変更(Dirty)アセットのみが保存されます
    * @param [in]  bPromptToSave       trueの場合はアセットの保存確認を求められます。falseの場合は全て保存されます。
    */
    static void ExecuteSaveAssets(const TArray<UObject*>&  InTargets,
                                  bool                     bCheckDirty = true,
                                  bool                     bPromptToSave = false);
};

実装

#include "AssetUtilityFunctionLibrary.h"

#if WITH_EDITOR
#include "FileHelpers.h"
#endif // WITH_EDITOR

void
UAssetUtilityFunctionLibrary::ExecuteSaveAssets(const TArray<UObject*>&  InTargets,
                                                const bool                bCheckDirty,
                                                const bool                bPromptToSave)
{
    TArray<UPackage*> PackagesToSave;
    for (UObject* Obj : InTargets)
    {
        if (IsValid(Obj))
        {
            if (Obj->HasAnyFlags(RF_Transient))
            {
                continue;
            }

            UPackage* Package = Obj->GetOutermost();
            if (IsValid(Package))
            {
                PackagesToSave.Add(Package);
            }
        }
    }

    if (0 < PackagesToSave.Num())
    {
        FEditorFileUtils::PromptForCheckoutAndSave(
            PackagesToSave,
            bCheckDirty,
            bPromptToSave);
    }
}
//---

最後に

直接FEditorFileUtils::PromptForCheckoutAndSave()を呼び出しても構わないのですが、個人的にUObjectで渡す方が使い勝手がいいと思ったので、簡単なラッパー関数を用意して使用しています。

MarkPackageDirty()の呼び出しだけだと保存まではされないため、保存したい場合はこのような処理を行う必要があります。
専用アセットを用意したり、アセットの変更を検知して別のアセットの変更を行ったりする場合に利用する機会があるかもしれません。