Unity无法序列化Dictionary对象,可以通过实现ISerializationCallbackReceiver接口,来自定义实现,即:搞一个key list和一个value list分别存储dict的k-v,于是属性面板上就会显示,k-v的两个列表。
但这会有一个问题,就是在面板上,增删k-v会导致报错,因为——增加会复制最后一个key值导致重复,删除会导致k-v列表不对应。
解决方案是,在反序列化回调OnAfterDeserialize中,去处理k-v的增删,即:需要生成唯一的key值,以及同步k-v列表的数量,然后写入dict。
先看,自定义实现ISerializationCallbackReceiver接口的泛型Dictionary。
[Serializable]
public abstract class SerializationDict<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{
[SerializeField]
private List<TKey> dictKeys = new();
[SerializeField]
private List<TValue> dictValues = new();
/// <summary>
/// Gets unique temporary key for the Inspector add new item.
/// </summary>
public abstract TKey GetUniqueTmpKey(int keyIndex);
/// <summary>
/// Copies the Dict to the serialization KVs.
/// </summary>
public void OnBeforeSerialize()
{
this.dictKeys .Clear();
this.dictValues.Clear();
foreach (var pair in this)
{
this.dictKeys .Add(pair.Key);
this.dictValues.Add(pair.Value);
}
}
/// <summary>
/// Copies the serialization KVs to the Dict.
/// </summary>
public void OnAfterDeserialize()
{
var keysCount = this.dictKeys.Count;
var valuesCount = this.dictValues.Count;
var diff = keysCount - valuesCount;
if (diff > 0)
{
if (keysCount > this.Count)
{
// Inspector add new keys requires to add values
for (var i = 0; i < diff; ++i)
{
var keyIndex = valuesCount + i;
this.dictKeys[keyIndex] = this.GetUniqueTmpKey(keyIndex);
this.dictValues.Add(default);
}
}
else if (keysCount == this.Count)
{
// Inspector remove values requires to remove keys
this.dictKeys.RemoveRange(keysCount - diff, diff);
keysCount = this.dictKeys.Count;
}
}
else if (diff < 0)
{
if (keysCount < this.Count)
{
// Inspector remove keys
}
else if (keysCount == this.Count)
{
// Inspector add new values requires to add keys
for (var i = diff; i < 0; ++i)
{
this.dictKeys.Add(this.GetUniqueTmpKey(this.dictKeys.Count));
}
keysCount = this.dictKeys.Count;
}
}
this.Clear();
for (int i = 0; i < keysCount; i++)
{
this.Add(this.dictKeys[i], this.dictValues[i]);
// this[this.dictKeys[i]] = this.dictValues[i];
}
}
}
- OnAfterDeserialize是反序列化回调,面板操作k-v列表后执行,此时需要将k-v数值写入dict。
- 由于是泛型key值,无法知道什么类型,所以需要给具体类去生成一个唯一key,即实现TKey GetUniqueTmpKey(int keyIndex)抽象方法。
- 利用GetUniqueTmpKey的返回值,来填充面板增加的key值,就可以避免写入dict时的key重复。
再来看,一个具体的实现类。
[Serializable]
public class SerializationSpriteDict : SerializationDict<string, Sprite>
{
public override string GetUniqueTmpKey(int keyIndex)
{
return "TmpKey" + keyIndex;
}
}
- 这个可序列化的Dictionary,是string-sprite的键值对。
- 在这个类中,可以返回字符串的唯一key值。
最后,任意具体类型的Dictionary都可以,继承SerializationDict<TKey, TValue>,再提供生成唯一key值的方法实现,这样属性面板就可以增删,这个Dictionary了。
当然,增删的k-v值,都是临时的,有意义的数值需要手动去填充——也可以用代码去填充,再用面板去查看与修改。