拿配置表的实现方式来说,
json查看清晰,修改方便(复制大法、ai生成)。但是读出来需要反序列化,用反射把表里的字段映射到目标类的成员上,消耗较大。
ScriptableObject读取代价小,读出来就是目标类型。但是配置量较大时操作繁琐,可读性也差(配置是长度好几十的数组,每一条又是个数据类,不可能纯手动编辑)。
之前看到有人在Editor目录下放了json、写脚本来读取json反序列化出对象,再拿这个对象序列化生成ScriptableObject到Runtime目录,似乎是一种两全其美的方式。
于是试了一下,遇到一个问题:
json过于灵活,一个json数组中可以上一个元素是int,下一个元素是int[]。那么这种字段反序列化出来的目标类型对应的目标成员就只能是object或者JToken这种通用的类型。
这样本来没啥问题,然而ScriptableObject就没那么大度了,object等通用类型无法再次存入它。虽然反序列化出来的对象可以直接对object成员进行赋值,能赋值进去,但是当AssetDatabase.SaveAssets()后,再读出来发现本地生成的ScriptableObject里那个字段依然是null。你以为赋值成功了,实际并没有保存。
没啥太好的解决方案。只能是在目标类型里额外加一个能兼容多种情况的成员。比如成员object a可能是int和int[],那就额外加一个int[] b。a可能是int和string,那b就是string。在json反序列化完成后解析a来赋值b,将b存入ScriptableObject,需要用时以b的类型为准。
这个时机就是OnDeserialized的时候。
public object num; //注意:从json反序列化时,生成object是可以的,但是object无法再次序列化存入ScriptableObject。
public int[] range; //所以在存ScriptableObject时统一用数组。
// 反序列化时,将object类型生成int[]。
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
if (num is int item)
{
range= new int[] { item };
}
else if (num is long l)
{
range= new int[] { (int)l };
}
else if (num is JArray array)
{
range= array.ToObject<int[]>();
}
}
虽然看起来多麻烦了一个小函数的程度,但是其实不管是只用json还是只用SO都绕不开业务上的类型兼容问题,只不过它们的麻烦之处不在构建这,可能在写配置时 可能在使用配置时。所以总的来说这样还是比较好用的。