2016年5月2日月曜日

[Unity]ポリモーフィズムなScriptableObjectのJSONシリアライズに挑戦する

クラス階層構造のあるScriptableObjectをJSONシリアライズしたいというお話。
もっといい方法があるなら教えてください。切実に。

シリアライズされたオブジェクトは型情報を含まない

含みません。ということはデシリアライズするときに意図した型に復元することはできません。
Itemクラスを継承したWeaponクラスのインスタンスをシリアライズしたとして、
復元時にはそのアイテムが武器なのか消費アイテムなのか判断がつかないということです。

ゲーム中に変更の無い情報はアセットのパスをシリアライズすれば事足りる

考えた構造がこちら。
SerializableItemクラスはシリアライズ専用のクラスです。
ItemのインスタンスをResourcesに保持しておき、そのパスをアイテムに持たせておくという構造です。
これによって、ScriptableObject.Instantiate(Resources.Load(assetPath))で型情報を復元することができます。

型名をシリアライズしておいてリフレクションで復元するという方法もありますが、固定の情報(ここでいうとIronSwordのpower)のリストアが手間だったりするので、こちらのほうがスマートでしょう。

インスタンスにいちいちassetPathを設定するのが面倒と思われるかもしれませんが、エディタ拡張で自動化すれば手間にはなりません。
projectウィンドウが更新されたときにすべてのItem型アセットを更新して保存するようにします。
[InitializeOnLoadMethod]
public static void RefreshItemAssetPath() {
EditorApplication.projectWindowChanged += () => {
foreach (string guid in AssetDatabase.FindAssets("t:Item")) {
string path = AssetDatabase.GUIDToAssetPath(guid);
Item item = AssetDatabase.LoadAssetAtPath<Item>(path);
Match match = new Regex(@"Assets\/Resources\/(?<resource_path>.*)\.asset").Match(path);
if (!match.Success) {
Debug.Log("Cannot extract resource Path. : " + path);
return;
}
string resourcePath = match.Groups["resource_path"].Value;
item.assetPath = resourcePath;
}
AssetDatabase.SaveAssets();
};
}


ゲーム中で動的に変化する情報はどうする?

例えば武器に強化値があったとします。この情報はゲーム内で動的に変化するため、アセットに持たせるわけにはいかず、シリアライズの必要があります。
これに対してはいい方法が思いついていません。一応動作する実装は可能ですが...

現状の実装はこんな感じになっています。
サブクラスに固有のフィールドをExtensionクラスのインスタンスとkeyで紐付けて保持する。
で、シリアライズするときはオーバーライドされたCreateExtensionsでExtensionのリストを作ってSerializableItemに持たせ、デシリアライズするときはオーバーライドされたRestoreExtensonsで各フィールドに値を設定しなおすってことをやってます。
typeのenumはint, float, bool, stringの4種類で事足りるはず。(足りないものはJson化してstringとして保持することが可能なはず)

まとめ

  • アセットのパスをシリアライズしとけば型を復元できるよ
  • もっといい方法があれば教えてください

0 件のコメント:

コメントを投稿