ItemやMobileを継承している全てのスクリプトに
public override void Deserialize( GenericReader reader ) public override void Serialize( GenericWriter writer )
という記述を見つけることが出来ます。 Derializeはロード、Serializeはセーブ時に使用されるメソッドであり、これを使うことによりItemやMobileのデータを保存することが出来るのです。
まずデータがどのようにセーブされているのかを説明します。 RunUOのセーブデータはバイナリで保存されています。例えるなら一本のテープに数値を書き込んでいるようなものです。 あるItemに整数(int型)のA,Bというデータをセーブしようとすると
int A | int B |
int型 | int型 |
のようなデータが書き込まれます。書き込まれたデータに元の変数名などは含まれません。 そこで順番が重要になるのです。今回はA,Bの順番で書き込みます。
そうした保存するデータや順番をSerializeに記述します。
それをロードするときにはセーブした長さと同じ長さの入れ物を用意しなければなりません。
セーブされているデータには元の変数、つまりセーブ前がA,Bだった等の情報は含まれ居ません。セーブした順番を通りに読み込むことで正しく復元することが出来るのです。
int型 | int型 |
int A | int B |
順番を間違えてC,B,Aと逆に読み込んだとします。 するとデータは
int A | int B |
int型 | int型 |
↓
int型 | int型 |
int B | int A |
と保存したときと違う変数に復元されてしまいます。この場合はAとBの値が入れ替わってしまいました。ちなみにAとB型が共にint型なのでエラーは発生しません。 順番は間違えないでください。
例として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 A | int B | string C |
と読み込んでいるのに対して
int型 | int型 |
しか存在しないので読み込めないのです。 そのため今まで存在したアイテムを削除するしかない、訳ではありません。 そこで使用するのがversionです。
writer.Write( (int) 0 ); // version
int version = reader.ReadInt();
の記述をあちこちで見たことはありませんか。 これはバージョンをチェックすることでこの問題を回避するためにあるのです。
versionは普通のint型で特性などはありません。 ただし、必ずセーブ時のデータの先頭になければなりません。 データを追加するたびに数字を増やします。
今回の場合,整数A,Bのみの時のversionを1,文字列Cを追加したときを2とします。 すると
version 1 | |||
---|---|---|---|
version 1 | int型 | int型 | |
version 2 | |||
version 2 | int型 | int型 | string型 |
のようにデータがセーブされます。
ロード時のポイントはこのバージョンによって読み込むときの入れ物を変更する点にあります。
先頭にあるint型のデータつまりversionを読み込み 1ならば
int A | int B |
2ならば
int A | int B | string C |
で読み込むことによりデータが足りないことによるエラーを防ぎます。
注意:ここでは新しいデータを後に追加するように書いていますが、実際には新しいデータは前に追加します。caseの使い方を見ることで理解できるかと思います。
#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;
}
}
}
}}