C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

UE4/C++: USaveData 系のセーブデータの Slot 群をすべて取得する方法

USaveData 派生型のセーブデータを作り UGameplayStatics::SaveGameToSlot, UGameplayStatics::LoadGameFromSlot を用いてセーブとロードを実装する手法を用いるとそれ自体の実装労力は節約できて嬉しい。しかし、これらの APIUE4 プロジェクトの開発時に文字列型の Slot が確定している前提で設計されているらしく、実行時にユーザーが任意の Slot 名称を与えるユースケースではその一覧の取得を行う手段が無い(UE4-4.21現在)。

このためもあってか、一般的に多くの UE4 プロジェクトでは、Slot を開発時に幾つかの固定の名称か、あるいは連番を文字列化して処理する仕組みが採用される例が多いように見える。実際にゲームとして流通するアプリでもセーブデータがスロット0番からN番のように連番で用意されている例を多く見かける(個人の感想です)。一般的に幾つかのセーブスロットを用意する方式で構わないアプリではそうした対応で、簡単かつユーザーにとっても十分で実用にも問題無く快適に使用できる。

しかし、そうではない、ユーザーに可能な限り任意性の高い Slot 名称を与えたい仕組みが必要なアプリを作りたい場合もある。このような場合には、次の様にして実行中の UE4 アプリのセーブデータの Slot の一覧を取得する実装をプロジェクトへ実装する:

// MySaveDataUtility.h
#pragma once

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

UCLASS()
class UMySaveDataUtility : public UBlueprintFunctionLibrary
{ GENERATED_BODY()
public:
  UFUNCTION( BlueprintCallable, BlueprintPure ) static TArray< FString > GetSaveSlots();
};
// MySaveDataUtility.cpp
#pragma once

#include "MySaveDataUtility.h"
#include "Runtime/Engine/Public/PlatformFeatures.h"
#include "Runtime/Core/Public/HAL/PlatformFilemanager.h"
#include "Runtime/Core/Public/Misc/LocalTimestampDirectoryVisitor.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"

DEFINE_LOG_CATEGORY_STATIC( MySaveDataUtility , Log, All );

TArray< FString > UMySaveDataUtility::GetSaveSlots()
{
  TArray< FString > Slots;

  if ( ! IPlatformFeaturesModule::Get().GetSaveGameSystem() )
  {
    UE_LOG( MySaveDataUtility, Error, TEXT( "GetSaveSlots: GetSaveGameSystem was failed." )
    return { };
  }

  TArray< FString > Empty;
  IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
  FLocalTimestampDirectoryVisitor Visitor( PlatformFile, Empty, Empty, false );
  PlatformFile.IterateDirectory( *( FPaths::ProjectSavedDir() / TEXT( "SaveGames" ) ), Visitor );
  for ( TMap< FString, FDateTime >::TIterator i( Visitor.FileTimes ); i; ++i)
  {
    const FString FilePath = i.Key();
    const FString FileName = FPaths::GetCleanFilename( FilePath );
    
    if ( FPaths::GetExtension( FileName, false ).Equals( TEXT( "sav" ), ESearchCase::IgnoreCase) )
      Slots.Emplace( FPaths::GetBaseFilename( FileName ) );
  }

  return Slots;
}

セーブデータのディレクトリーを浚い、 .sav 拡張子のファイルベース名部分を Slot 名称として回収してセーブデータ群の Slot の一覧を取得している。泥臭い(´・ω・`)

別法としてはそもそも Slot を使わずに UGameplayStatics::SaveGameFromMemory, UGameplayStatics::LoadGameFromMemory を使い、 UE4 が用意する Saved/SaveGames へのファイル保存の仕組みを使わずにプログラマーが直接ファイルシステムなり保存先のデータベースなりオンラインのストレージなり何なりの制御を実装してセーブデータをシリアライズする方法もある。と、言っても、そのような必要な必要がある場合は、そもそも USaveData を使わずに UE4 とは独立したセーブデータのシリアライザーを実装した方が善い場合が多いかもしれない。

References

  1. Is there a way to get all savegames in BP - UE4 AnswerHub
  2. UGameplayStatics | Unreal Engine
  3. Saving Your Game | Unreal Engine