3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UE5 UObjectをセーブロードしてみる

Posted at

概要

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;
};

これで IntParameterFloatParameterがセーブ対象になります。

セーブロード処理

作成したセーブゲームクラスを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を継承したクラスにシリアライザ処理を書き対象のプロパティを入れます。それと同時にそのシリアライズデータを格納するための構造体を用意します。
以下サンプルコード。

セーブする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をセーブ対象としつつそのまま扱いたいケースもあるかと思いますが、手順が煩雑なので使いどころに注意が必要だと思います。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?