1. 为什么Unity的字典在Inspector里“隐身”了?
如果你在Unity里用过C#的Dictionary<TKey, TValue>,肯定遇到过这个让人头疼的问题:在Inspector面板里,你声明的字典字段空空如也,只有一个冷冰冰的“Dictionary”标签,点也点不开,改也改不了。这感觉就像你买了个高级工具箱,结果最重要的那把锁是焊死的,根本用不了。
这其实不是Unity的Bug,而是它设计上的一个“特性”。Unity的序列化系统(就是负责把内存中的对象状态保存成文件,或者从文件加载回内存的那套机制)有自己的一套规矩。它主要支持序列化一些“简单”的类型,比如int、float、string,或者标记了[Serializable]的类。而对于像Dictionary这样的泛型集合,Unity的序列化系统是“不认识”的。它不知道如何把里面错综复杂的键值对关系,一个个拆开、打包、再还原。所以,它干脆选择了无视,不把它画到Inspector面板上,也不把它保存到场景或预制体文件里。
这在实际开发中简直是个灾难。想象一下,你要做一个技能配置表,每个技能ID对应一个伤害值。用字典是最自然的选择,代码里用起来也顺手。但到了配置环节,你没法在编辑器里直观地添加、删除或修改这些键值对,只能硬编码在脚本里,或者另写一个复杂的配置文件解析逻辑。每次改个数值都要重新编译,效率低得让人抓狂。
所以,我们今天的核心目标,就是给这把“焊死的锁”配一把万能钥匙,让字典不仅能被序列化保存,还能在Inspector里像列表一样友好地展示和编辑。我在这上面踩过不少坑,也试过很多方法,今天就把最靠谱、最高效的两种方案掰开揉碎了讲给你听。
2. 方案一:拥抱原生接口 ISerializationCallbackReceiver
这是最“正统”的Unity解决方案,不依赖任何第三方插件,完全利用Unity引擎自身提供的扩展能力。它的核心思想是“曲线救国”:既然Unity不会直接序列化字典,那我们就自己动手,在序列化前把字典拆成两个Unity认识的列表(一个存键,一个存值);在反序列化后,再把这两个列表组装回字典。
2.1 核心原理:序列化前后的“中间人”
这一切的魔法都源于一个接口:ISerializationCallbackReceiver。这个接口名字有点长,但功能很直白——它允许你的类在序列化(保存)之前和反序列化(加载)之后,插入一些自定义的操作。
你可以把它想象成一个负责打包和拆包的物流专员。当Unity要把你的对象装箱发走(序列化)时,专员会先跑过来(OnBeforeSerialize),把字典里杂乱的衣服(键值对)叠整齐,分别放进“键箱子”和“值箱子”(两个List)。这样,Unity的搬运工(序列化系统)就能顺利搬走这两个标准的箱子。当货物运到目的地,Unity拆开箱子(反序列化)后,专员又会跑过来(OnAfterDeserialize),从两个箱子里把衣服拿出来,重新挂回衣橱(字典)里。
下面我们来看一个我项目中常用的、经过实战考验的可序列化字典类。这个类是个泛型类,可以适配任何类型的键和值(只要它们本身能被Unity序列化)。
[Serializable]
public class SerializedDictionary<TKey, TValue> : ISerializationCallbackReceiver
{
// 这是真正在代码里使用的字典
private Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
// 这两个列表是给Unity序列化看的“替身”
[SerializeField] private List<TKey> _keys = new List<TKey>();
[SerializeField] private List<TValue> _values = new List<TValue>();
// 对外暴露字典的访问,保持和原生Dictionary类似的API
public Dictionary<TKey, TValue> Dictionary => _dictionary;
public void OnBeforeSerialize()
{
// 在序列化前,将字典的内容同步到两个列表
_keys.Clear();
_values.Clear();
foreach (var kvp in _dictionary)
{
_keys.Add(kvp.Key);
_values.Add(kvp.Value);
}
}
public void OnAfterDeserialize()
{
// 在反序列化后,用两个列表的数据重建字典
_dictionary.Clear();
// 防止键值数量不一致导致崩溃,取最小的那个
int count = Mathf.Min(_keys.Count, _values.Count);
for (int i = 0; i < count; i++)
{
// 这里有个坑:如果_keys里有重复的键,Add会抛出异常。
// 在实际项目中,你可能需要根据业务逻辑决定是覆盖、跳过还是报错。
if (!_dictionary.ContainsKey(_keys[i]))


1790

被折叠的 条评论
为什么被折叠?



