「SKSE上でのデータのセーブ/ロード」の編集履歴(バックアップ)一覧に戻る

SKSE上でのデータのセーブ/ロード - (2018/09/07 (金) 20:53:15) のソース

#contents

*目的
SKSEプラグインを用いてデータの保存を行えるサンプルを提供します。

SKSEプラグイン上で処理するデータは、静的変数を用いればゲーム中は値を保持し続けますが、ゲーム終了時にはすべて破棄されます。

このため本来はセーブデータにデータを保存する([[Tips参照>https://www50.atwiki.jp/skyrim_mod/pages/18.html#id_717ea3ed]])必要がありますが、このページのサンプルによりSKSEプラグイン上でデータの保存が可能となりPapyrus上の変数などに戻すような手間が不要になります。

*前提
[[SKSE64プラグイン開発環境構築手順]]にて、skse 本体のコンパイルができる状態にあることが前提となります。

プラグインのサンプルプロジェクトである samplePlugin を元にデータのセーブ/ロードを実装します。

*手順概要
データの保存のために、SKSESelrializationInterfaceクラスが提供されています。これを利用してデータのセーブ/ロードを実現します。

以下の手順によって実装します。

+SKSESerizalizationIntarfaceのインスタンス化
--自分のSKSEプラグインでSKSESerializationInterfaceを利用できるようにします。
+セーブ/ロード時のコールバック関数の作成と登録
--セーブ時及びロード時に動作させる関数を作成し、実際に呼び出されるよう登録します。
+セーブ用関数の実装
--SKSEプラグイン内の変数の値を保存する処理を実装します
+ロード用関数の実装
--SKSEプラグイン内の変数へ値を読み込ませる処理を実装します

*手順詳細
以下の手順はすべてsamplePluginプロジェクトのmain.cppに対して行います。
**SKSESerializationInterfaceのインスタンス化
グローバル変数としてSKSESerializationInterfaceのポインタを作成します。

 IDebugLog	gLog;
 UInt32 g_skseVersion;
 PluginHandle	g_pluginHandle = kPluginHandle_Invalid;
 SKSEMessagingInterface	* g_messaging = nullptr;
 //*************SKSESerializationIntarfaceポインタ作成*************
 SKSESerializationInterface	* g_serialization = NULL;
 
 void SKSEMessageHandler(SKSEMessagingInterface::Message* msg)
 // ...以下省略...
&br()
宣言したg_serialization変数をSKSEPlugin_Query関数の実行時にインスタンス化させます。

 extern "C"
 {
 bool SKSEPlugin_Query(const SKSEInterface * skse, PluginInfo * info)
 // ...前半省略...
 	g_messaging = (SKSEMessagingInterface *)skse->QueryInterface(kInterface_Messaging);
 	if (!g_messaging)
 	{
 		return false;
 	}
 
 	/*************追加部分*************/
 	// get the serialization interface and query its version
 	g_serialization = (SKSESerializationInterface *)skse->QueryInterface(kInterface_Serialization);
 	if (!g_serialization)
 	{
 		_MESSAGE("couldn't get serialization interface");
 
 		return false;
 	}
 
 	if (g_serialization->version < SKSESerializationInterface::kVersion)
 	{
 		_MESSAGE("serialization interface too old (%d expected %d)", g_serialization->version, SKSESerializationInterface::kVersion);
 
 		return false;
 	}
 	/*************追加部分*************/
 
 	return true;
 }

**セーブ/ロード時のコールバック関数の作成と登録
セーブ/ロード時に実行させるための関数を実装します。
 void Serialization_Save(SKSESerializationInterface * intfc)
 {
 }

 void Serialization_Load(SKSESerializationInterface * intfc)
 {
 }

 extern C {
 // ...以下省略...
&br()
上記二つの関数を、SKSESerializationInterface(g_seriarization変数)に登録し、実際にセーブやロードを実行した際に呼び出されるようにします。
この処理は SKSEPlugin_Load内に実装します。

-ここで最初に実行しているSetUniqueIDメソッドですが、第二引数の文字列がプラグインの識別子として動作し、異なるSKSEプラグイン間でのデータの誤ったロードを防ぐようになっていると思われます(''未確認'')

-サンプルでは'TEST'としていますが、自作のMODで実装する際は異なる単語にした方が良いです。

 bool SKSEPlugin_Load(const SKSEInterface * skse)
 {
 	if (g_messaging)
 	{
 		g_messaging->RegisterListener(g_pluginHandle, "SKSE", SKSEMessageHandler);
 	}
 	/*************追加部分*************/
 	// ### this must be a UNIQUE ID, change this and email me the ID so I can let you know if someone else has already taken it
 	g_serialization->SetUniqueID(g_pluginHandle, 'TEST');
 
 	g_serialization->SetSaveCallback(g_pluginHandle, Serialization_Save);
 	g_serialization->SetLoadCallback(g_pluginHandle, Serialization_Load);
 	/*************追加部分*************/
 
 	return true;
 }


**セーブ用関数の実装

セーブ用関数Serialization_Save上でセーブ処理を実装します。

セーブ処理には引数として受け取るintfcの、以下の2つのメソッドを利用します。
|OpenRecord|SKSEプラグイン内で保存するデータの住み分けやプラグイン更新時の古いデータとの整合性保持を行う|
|WriteRecordData|変数に格納されたデータを実際に保存する|

&br()
実装サンプルは以下のようになります。ここではtestInt変数内の「64」という数字を保存しています。

 const UInt32 kSerializationDataVersion = 1; // グローバルで宣言
 
 void Serialization_Save(SKSESerializationInterface * intfc)
 {
 	_MESSAGE("save");
 
 	SInt32 testInt = 64;
 
 	if (intfc->OpenRecord('DATA', kSerializationDataVersion))
 	{
 		intfc->WriteRecordData(&testInt, sizeof(testInt));
 		_MESSAGE("save DATA... int:%ld", testInt);
 	}
 }

-OpenRecordメソッドは、以降にWriteRecordDataにより保存するデータに対して識別子とバージョン情報を付与します。
|第一引数|識別子。文字列を登録する。|
|第二引数|バージョン情報。ロード時にも用いるためグローバル変数を利用することを推奨。|

&br()
-WriteRecordDataメソッドにより、変数内の値を保存します。
|第一引数|保存する値を格納した変数のポインタ|
|第二引数|第一引数の変数のサイズ|

**ロード用関数の実装

ロード用関数Serialization_Load上でロード処理を実装します。

ロード処理には引数として受け取るintfcの、以下の2つのメソッドを利用します。
|GetNextRecordInfo|保存時のOpenRecordメソッドで設定したパラメータを引き出す|
|ReadRecordData|保存されているデータを読み込んで変数に格納する|

&br()
実装サンプルは以下のようになります。ここではtestInt変数に保存した値を読み込んでいます。

 void Serialization_Load(SKSESerializationInterface * intfc)
 {
 	_MESSAGE("load");
 
 	UInt32	type;
 	UInt32	version;
 	UInt32	length;
 	bool	error = false;
 
 	while (!error && intfc->GetNextRecordInfo(&type, &version, &length))
 	{
 		switch (type)
 		{
 		case 'DATA':
 		{
 			if (version == kSerializationDataVersion)
 			{
 				SInt32 testInt;
 				if (!intfc->ReadRecordData(&testInt, sizeof(testInt)))
 				{
 					error = true;
 					return;
 				}
 
 				_MESSAGE("read DATA... int:%ld", testInt);
 			}
 			else
 			{
 				error = true;
 			}
 		}
 		break;
 
 		default:
 			_MESSAGE("unhandled type %08X", type);
 			error = true;
 			break;
 		}
 	}
 }

-GetNextRecordInfoメソッドは、OpenRecordメソッドで付与した情報を引き出してそれぞれ変数に格納します。
|第一引数|識別子の値を引数に格納します。このサンプルでは「DATA」です。|
|第二引数|バージョン情報の値を引数に格納します。このサンプルでは「1」です。|
|第三引数|登録したデータのサイズを取得できるようです。このサンプルではReadRecordDataメソッドの第二引数に入れても使えますが、複数のデータの保存には対応できないので使用しません([[セーブ/ロードするデータの順序>https://www50.atwiki.jp/skyrim_mod/pages/77.html#id_3ddfff6f]]参照)|
&br()
-WriteRecordDataメソッドにより、変数内の値を保存します。
|第一引数|保存する値を格納した変数のポインタ|
|第二引数|第一引数の変数のサイズ|

*Tips
**保存するデータの型について
**セーブ/ロードするデータの順序
**LE版Skyrimでの実装について
目安箱バナー