Tips5


SerializeとDeserialize データのセーブとロード

  • カテゴリー: スクリプト
  • 重要性: 重要
  • 投稿日: 2005-07-29 (金) 13:24:41

メッセージ

ItemやMobileを継承している全てのスクリプトに

public override void Deserialize( GenericReader reader )
public override void Serialize( GenericWriter writer )

という記述を見つけることが出来ます。 Derializeはロード、Serializeはセーブ時に使用されるメソッドであり、これを使うことによりItemやMobileのデータを保存することが出来るのです。

基本

まずデータがどのようにセーブされているのかを説明します。 RunUOのセーブデータはバイナリで保存されています。例えるなら一本のテープに数値を書き込んでいるようなものです。 あるItemに整数(int型)のA,Bというデータをセーブしようとすると

int Aint B
int型int型

のようなデータが書き込まれます。書き込まれたデータに元の変数名などは含まれません。 そこで順番が重要になるのです。今回はA,Bの順番で書き込みます。

そうした保存するデータや順番をSerializeに記述します。

それをロードするときにはセーブした長さと同じ長さの入れ物を用意しなければなりません。

セーブされているデータには元の変数、つまりセーブ前がA,Bだった等の情報は含まれ居ません。セーブした順番を通りに読み込むことで正しく復元することが出来るのです。

int型int型
int Aint B

順番を間違えてC,B,Aと逆に読み込んだとします。 するとデータは

int Aint B
int型int型

int型int型
int Bint A

と保存したときと違う変数に復元されてしまいます。この場合はAとBの値が入れ替わってしまいました。ちなみにAとB型が共にint型なのでエラーは発生しません。 順番は間違えないでください。

まとめ
A,Bの順番にSerializeしたら必ずA,Bの順番にDeserializeしましょう。

実際にスクリプトを見てみよう。

例としてScripts\Items\Wands\BaseWand.csについて見てみましょう。 このスクリプトはマジックワンドのクラスについて記述されています。

ワンドは魔法の種類と、チャージ数の2つの値を保存しています。

#code(csharp){{
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );

writer.Write( (int) 0 ); // version

writer.Write( (int) m_WandEffect );//魔法の種類の書き込み
writer.Write( (int) m_Charges );//チャージ数の読み込み
}
}} Serializeでは、m_WandEffect,m_Chargesの順番でセーブしています。

#code(csharp){{
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );

int version = reader.ReadInt();

switch ( version )
{
case 0:
{
m_WandEffect = (WandEffect)reader.ReadInt();//魔法の種類の読み込み
m_Charges = (int)reader.ReadInt();//チャージ数の読み込み

break;
}
}
}
}} Deserializeでもm_WandEffect,m_Chargesの順番でロードしています。

データの追加

先ほど使用したItemにA,Bに加えて新しくCという文字列(string型)を追加してみましょう。 Serialize,DeserializeにCの情報を追加するといきなり問題が発生します。 ロード時に今まで存在したItemにエラーが発生します。 それはSerializeの記述でデータを

int Aint Bstring C

と読み込んでいるのに対して

int型int型

しか存在しないので読み込めないのです。 そのため今まで存在したアイテムを削除するしかない、訳ではありません。 そこで使用するのがversionです。

writer.Write( (int) 0 ); // version
int version = reader.ReadInt();

の記述をあちこちで見たことはありませんか。 これはバージョンをチェックすることでこの問題を回避するためにあるのです。

versionは普通のint型で特性などはありません。 ただし、必ずセーブ時のデータの先頭になければなりません。 データを追加するたびに数字を増やします。

今回の場合,整数A,Bのみの時のversionを1,文字列Cを追加したときを2とします。 すると

version 1
version 1int型int型
version 2
version 2int型int型string型

のようにデータがセーブされます。

ロード時のポイントはこのバージョンによって読み込むときの入れ物を変更する点にあります。

先頭にあるint型のデータつまりversionを読み込み 1ならば

int Aint B

2ならば

int Aint Bstring C

で読み込むことによりデータが足りないことによるエラーを防ぎます。

注意:ここでは新しいデータを後に追加するように書いていますが、実際には新しいデータは前に追加します。caseの使い方を見ることで理解できるかと思います。

まとめ
新しくデータを追加するときはversionの数字を上げて対応する。
versionはデータのセーブ、ロードが修正された回数。

サンプル

#code(csharp){{
private int a,b;
private string c;

public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );

writer.Write( (int) 2 ); // version

writer.Write( c );
writer.Write( (int) b );
writer.Write( (int) a );
//version,c,b,aの順番に記述
}

public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );

int version = reader.ReadInt();//必ず最初にバージョンを読み込む

switch ( version )
{
case 2:
{
c = reader.ReadString();
goto case 1;
}
case 1:
{
b = reader.ReadInt();
a = reader.ReadInt();

goto case 0;
}
case 0:
{
break;
}
}
}
}}


  • 説明が難しい上、長いことこの上ない。重要なのに誰も説明しないのはそういう訳だったらしい。 -- Hermit? 2005-07-29 (金) 15:01:18
  • とても助かります、ありがとうございました。 -- A? 2005-08-05 (金) 05:52:24

メニュー

オリジナル

T2A

  • InPorYelm?

UOR+T2A

AOS

  • なし

UOML

 

  • counter: 715
  • today: 1
  • yesterday: 0
  • online: 1