UE4 SaveGame

※ セーブやロードの誤ったやりかたを書いていたので書きなおしました。2021/06/24

間違ってるところがあったら教えてください。

環境

・UE 4.26.2

注意点

UE4でゲームをセーブする機能を作る時USaveGameクラスを使う。 使うときの注意点

USaveGameクラスの変数にはUPROPERTY()をつけないと保存されない。

HogeSaveGame.h

UCLASS()
class MYPROJECT_API UHogeSaveGame : USaveGame
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite, Category = SaveGame)
        FString Id;
};

セーブ/ロードのやりかた

例だからGameInstance上に作るけど、場合によってはPlayerControllerだったり、GameModeだったりいろいろな場所に作ると思うよ。
GameInstanceを継承したクラスを作ってHogeGameInstanceをつくる。
HogeGameInstance.hに追加

UCLASS()
class MYPROJECT_API UHogeGameInstance : public UGameInstance
{
    GENERATED_BODY()
public:
    static UHogeGameInstance* Get();

    UHogeSaveGame* GetHogeSaveGame(const FString& SlotName, const int32& SlotIndex);
    void Save(const FString& SlotName, const int32& SlotIndex);
    void Load(const FString& SlotName, const int32& SlotIndex);
};

HogeGameInstance.cppに追加

#include "HogeSaveGame.h"
#include "Kismet/GameplayStatics.h"

UHogeGameInstance* UHogeGameInstance::Get()
{
    UHogeGameInstance* instance = nullptr;

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

    return instance;
}


UHogeSaveGame* UHogeGameInstance::GetHogeSaveGame(const FString& SlotName, const int32& SlotIndex)
{
    if (UGameplayStatics::DoesExistsSaveGame(SlotName, SlotIndex))
    {
        return Cast<UHogeSaveGame>(UGameplayStatics::LoadGameFromSlot(SlotName, SlotIndex));
    }
    return nullptr;
}

void UHogeGameInstance::Save(const FString& SlotName, const int32& SlotIndex)
{
    UHogeSaveGame* HogeSaveGame = GetHogeSaveGame(SlotName, SlotIndex);
    if (HogeSaveGame == nullptr)
    {
        HogeSaveGame = Cast<UHogeSaveGame>(UGameplayStatics::CreateSaveGameObject(UHogeSaveGame::StaticClass()));
    }
    
    HogeSaveGame->Id = huga;

    UGameplayStatics::SaveGameToSlot(HogeSaveGame, SlotName, SlotIndex);
}

void UHogeGameInstance::Load(const FString& SlotName, const int& SlotIndex)
{
    USaveGame* HogeSaveGame = GetHogeSaveGame(SlotName, SlotIndex);
    if (HogeSaveGame != nullptr)
    {
        huga = HogeSaveGame->Id;
    }
}

SlotNameSaveGameクラス毎に変えないと保存されない。
例えば、
一人用のゲームで、敵キャラの位置とか体力を保存するEnemySaveGameと、プレイヤーの情報を保存するPlayerSaveGameがあるとき、

UKismetGameplay::SaveGameToSlot(EnemySaveGame, "Slot1_Enemy", 0);
UKismetGameplay::SaveGameToSlot(PlayerSaveGame, "Slot1_Player", 0);

みたいにする必要がある。
Slot1の部分は、ドラクエで言う冒険の書1てきなかんじ。
f:id:pto8913:20210626120039p:plain
ゲームのセーブ/ロード画面で選択されたときに決める~

マルチプレイのゲームの場合、プレイヤーの情報を保存するPlayerSaveGameがあるとき、

UKismetGameplay::SaveGameToSlot(PlayerSaveGame, "PlayerID", 0);

みたいにプレイヤーのIDで保存することになると思うよ。
UserIndexはいつ使うの?って思い、ソースコードをのぞいてみた結果Userindexが、 引数として存在してるだけで使われている場所がみつかりませんでした。
探した場所
UE_4.26\Engine\Source\Runtime\Engine\Public\SaveGameSystem.h
SaveとLoadの実装されてる部分

virtual bool SaveGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, const TArray<uint8>& Data) override
{
    return FFileHelper::SaveArrayToFile(Data, *GetSaveGamePath(Name));
}
virtual bool LoadGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, TArray<uint8>& Data) override
{
    return FFileHelper::LoadFileToArray(Data, *GetSaveGamePath(Name));
}

なのでマルチプレイでセーブ/ロードするときはプレイヤーのIDをSlotNameとして使う必要がありそうだよ。
もしUserIndexを使ってる場所があったら教えてください。

非同期セーブ/ロード

SaveGameToSlotAsyncSaveGameToSlot
LoadGameFromSlotAsyncLoadGameFromSlot
に変えるだけでできる。
セーブするときは非同期を使うことが多いと思いました。
セーブ/ロードが完了したときにイベントを呼び出すこともできる!
デリゲートというものが使われているので詳しく知りたい人は
これを
docs.unrealengine.com

HogeGameInstance.cpp

void UHogeGameInstance::Save(const FString& SlotName, const int& SlotIndex, FAsyncSaveGameToSlotDelegate CallBack = FAsyncSaveGameToSlotDelegate())
{
    UHogeSaveGame* HogeSaveGame = GetHogeSaveGame(SlotName, SlotIndex);
    if (HogeSaveGame == nullptr)
    {
        HogeSaveGame = Cast<UHogeSaveGame>(UGameplayStatics::CreateSaveGameObject(UHogeSaveGame::StaticClass()));
    }
    
    HogeSaveGame->Id = huga;

    UGameplayStatics::AsyncSaveGameToSlot(HogeSaveGame, SlotName, SlotIndex, CallBack);
}

セーブ/ロード完了時のイベントの登録

HugaActor.h

UCLASS()
class MYPROJECT_API AHugaActor : AActor
{
    GENERATED_BODY()
public:
    void BeginSave();

    /* デリゲートに登録したい関数はUFUNCTIONを付ける必要がある */
    UFUNCTION()
        void SaveCompleted(const FString& SavedSlotName, const int32 SavedSlotIndex, bool bSuccess);
};

HugaActor.cpp

void AHugaActor::DoSave()
{
    UHogeGameInstance* GameInstance = UHogeGameInstance::Get();
    GameInstance->Save(
        SlotName,
        SlotIndex,
        FAsyncSaveGameToSlotDelegate::CreateUObject(
            this, &AHugaActor::SaveCompleted
        )
    );
}
void AHugaActor::SaveCompleted(const FString& SavedSlotName, const int32 SavedSlotIndex, bool bSuccess)
{
    UE_LOG(LogTemp, Log, TEXT("On AHugaActor SaveCompleted"));
    if (bSuccess)
    {
        UE_LOG(LogTemp, Log, TEXT("Save is Success"));
    }
}