概要
UnrealEngine5のセーブロード機能にて UObjectを継承したオブジェクトをセーブロードする方法のメモ書きです。
更新履歴
| 日付 | 内容 |
|---|---|
| 2025/11/25 | 初版 |
参考
以下を参考にさせて頂きました、ありがとうございます。
UE公式:ゲームの保存とロード
【UE5】SaveGameを使ってみよう!(C++編)
UnrealEngineでのオブジェクトデータの保存、読み込み
UE公式:FArchive::SetUEVer
環境
Windows11
Visual Studio 2022
UnrealEngine 5.6
関連ソース
"Engine\Source\Runtime\Core\Public\UObject\ObjectVersion.h"
セーブデータの作成とセーブロード
UnrealEngineのセーブロード関連は他に詳しい記事がありますので、詳しくはそちらを。
セーブゲームクラスを作成する
USaveGame を継承したクラスをつくります。
そこにセーブしたいプロパティを用意し、メタデータとしてSaveGameを付けます。
以下サンプルコード。
#include "GameFramework/SaveGame.h"
UCLASS()
class SAMPLE_API UMySaveGame : public USaveGame
{
GENERATED_BODY()
public:
UMySaveGame(){}
// パラメータ1
UPROPERTY(SaveGame)
int32 IntParameter = 0;
// パラメータ2
UPROPERTY(SaveGame)
float FloatParameter = 0.0f;
};
これで IntParameterとFloatParameterがセーブ対象になります。
セーブロード処理
作成したセーブゲームクラスをTestMySaveGame という名前でセーブします。
Saved/SaveGamesフォルダにセーブデータファイルが作られます。
以下サンプルコード。
static UMySaveGame* MySaveGame = nullptr;
// セーブデータがない場合作成する
if( !MySaveGame){
MySaveGame = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
}
// 指定名でセーブデータを作成する
UGameplayStatics::SaveGameToSlot(MySaveGame, TEXT("TestMySaveGame"), 0);
// ロードする
MySaveGame = Cast<UMySaveGame>(UGameplayStatics::LoadGameFromSlot(TEXT("TestMySaveGame"), 0));
if(MySaveGame){
// ロードしたデータを処理する
}
else{
// セーブデータがない場合作成する
MySaveGame = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
}
UObjectデータを扱う
セーブ対象は実体データなので通常はポインタなどはセーブされません。
UPROPERTY(SaveGame)
TObjectPtr<UMyObject> MyObject
上記のようポインタ保持で扱いたい場合はシリアライズしてセーブロードする方法があります。
UObjectクラスを作る
UObjectを継承したクラスにシリアライザ処理を書き対象のプロパティを入れます。それと同時にそのシリアライズデータを格納するための構造体を用意します。
以下サンプルコード。
// セーブロード対象にしたいオブジェクトクラス
UCLASS()
class SAMPLE_API UMyObject : public UObject
{
GENERATED_BODY()
public:
// ArがFMemoryWriterなら書き込みFMemoryReaderなら読み込みになる
virtual void SerializeData(FArchive& Ar) {
Ar << IntParameter;
Ar << FloatParameter;
}
// シリアライズデータ書き込み
void Save(TArray<uint8>& RawData) {
FMemoryWriter _Writer(RawData);
SerializeData(_Writer);
}
// シリアライズデータ読み込み
void Load(TArray<uint8>& RawData) {
FMemoryReader _Reader(RawData);
SerializeData(_Reader);
}
public:
UPROPERTY(SaveGame)
int32 IntParameter = 0;
UPROPERTY(SaveGame)
float FloatParameter = 0.0f;
};
// シリアライズデータを入れるための構造体
USTRUCT(BlueprintType)
struct FMyObjectSaveData {
GENERATED_BODY()
// シリアライズデータ
UPROPERTY(SaveGame)
TArray<uint8> SerializeData;
// セーブ
void SaveData(TObjectPtr<UMyObject> _Data){
if(_Data){ _Data->Save(SerializeData); }
}
// ロード
TObjectPtr<UMyObject> LoadData(){
TObjectPtr<UMyObject> _Data = NewObject<UMyObject>();
if(_Data){ _Data->Load(SerializeData); }
return(_Data);
}
};
セーブゲームクラスを作る
作成したUMyObjectと対応させてFMyObjectSaveDataを用意します。
UCLASS()
class SAMPLE_API UMySaveGame : public USaveGame
{
GENERATED_BODY()
public:
UMySaveGame(){}
// データ格納
void StoreData(){
MyObjectSaveData.SaveData(MyObject);
}
// データ抽出
void RestoreData(){
MyObject = MyObjectSaveData.LoadData();
}
// UObjectデータ
UPROPERTY(SaveGame)
TObjectPtr<UMyObject> MyObject = nullptr;
// UObject用セーブデータ
UPROPERTY(SaveGame)
FMyObjectSaveData MyObjectSaveData;
};
セーブロード時にデータの格納抽出を入れる
セーブロード処理にデータの格納と抽出処理を入れれば完了です。これで UMyObjectをポインタ保持しても保存されます。
static UMySaveGame* MySaveGame = nullptr;
// セーブデータがない場合作成する
if( !MySaveGame){
MySaveGame = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
}
// データを格納する
if(MySaveGame){
MySaveGame->StoreData();
}
// 指定名でセーブデータを作成する
UGameplayStatics::SaveGameToSlot(MySaveGame, TEXT("TestMySaveGame"), 0);
// ロードする
MySaveGame = Cast<UMySaveGame>(UGameplayStatics::LoadGameFromSlot(TEXT("TestMySaveGame"), 0));
if(MySaveGame){
// データを抽出する
MySaveGame->RestoreData();
}
else{
// セーブデータがない場合作成する
MySaveGame = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
}
配列で扱う場合
配列で扱う場合も基本的に一緒です。対応したセーブデータを用意しシリアライズ/デシリアライズを行い保存復元を行うことで保存が可能です。
以下サンプルコード。
UCLASS()
class SAMPLE_API UMySaveGame : public USaveGame
{
GENERATED_BODY()
public:
UMySaveGame(){}
// データ格納
void StoreData(){
for(auto _Obj : MyObjectList){
FMyObjectSaveData& _SaveData = MyObjectSaveDataList.AddDefaulted_GetRed(); // 配列に追加し参照取得
_SaveData.SaveData(_Obj); // 構造体に入れる
}
}
// データ抽出
void RestoreData(){
MyObjectList.Empty(); // リセット
for(FMyObjectSaveData& _SaveData : MyObjectSaveDataList){
MyObjectList.Add( _SaveData.LoadData() ); // デシリアライズしたデータからクラスを生成し配列に入れる
}
}
// UObjectデータ
UPROPERTY(SaveGame)
TArray<UMyObject> MyObjectList;
// UObject用セーブデータ
UPROPERTY(SaveGame)
TArray<FMyObjectSaveData> MyObjectSaveDataList;
};
その他
エンジンバージョンの付与について
参考にさせてもらった記事によりますと、UEのバージョンを付与しないとバージョン違いのデータを読み出せなくなったようです。
Reader.SetUEVer(FPackageFileVersion(static_cast<int>(EUnrealEngineObjectUE5Version::DATA_RESOURCES),
EUnrealEngineObjectUE5Version::DATA_RESOURCES));
このバージョン指定は書き込みの際も行った方が将来的な読み込みの際の判定で使えるようになるみたいです。
以下コード例
// 現在のエンジンバージョンで保存
FMemoryWriter _Writer(SaveData, true);
_Writer.SetUEVer(GPackageFileUEVersion);
MyObject->Serialize(_Writer);
// 現在のエンジンバージョンで読み込み
FMemoryReader _Reader(SaveData, true);
_Reader.SetUEVer(GPackageFileUEVersion);
MyObject->Serialize(_Reader);
まとめ
UObjectをセーブ対象としつつそのまま扱いたいケースもあるかと思いますが、手順が煩雑なので使いどころに注意が必要だと思います。